/* // $Id: //guest/julian_hyde/saffron/src/examples/saffron/test/ObjectSchemaTest.java#1 $ // Saffron preprocessor and data engine // Copyright (C) 2002 Julian Hyde <julian.hyde@mail.com> // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public // License along with this library; if not, write to the // Free Software Foundation, Inc., 59 Temple Place - Suite 330, // Boston, MA 02111-1307, USA. // // See the COPYING file located in the top-level-directory of // the archive of this library for complete text of license. // // jhyde, May 3, 2002 */ package saffron.test; import openjava.mop.OJClass; import openjava.ptree.*; import openjava.ptree.util.ParseTreeAction; import openjava.ptree.util.ParseTreePattern; import saffron.*; import saffron.Statement; import saffron.ext.AbstractTable; import saffron.ext.ReflectSchema; import saffron.opt.*; import saffron.rel.*; import saffron.util.RelEnvironment; import saffron.util.Util; import java.lang.reflect.Field; /** * A <code>ObjectSchemaTest</code> is a test harness for {@link ObjectSchema}. * Called from {@link ObjectSchema#suite} via reflection. * * @author jhyde * @since May 3, 2002 * @version $Id: //guest/julian_hyde/saffron/src/examples/saffron/test/ObjectSchemaTest.java#1 $ **/ public class ObjectSchemaTest extends SaffronTestCase { public ObjectSchemaTest(String s) throws Exception { super(s); } private static JavaReflectConnection reflect; private static JavaReflectConnection getReflect() { if (reflect == null) { reflect = new JavaReflectConnection(); } return reflect; } protected Object runQuery(String query) { return super.runQuery( query, new Statement.Argument[] { new Statement.Argument("reflect", getReflect())}); } // ------------------------------------------------------------------------ // tests follow public void _testFields() { // impossible to implement Object o = runQuery( "select from reflect.fields as field"); assertTrue(o instanceof Field[]); Field[] fields = (Field[]) o; assertEquals(10, fields.length); } public void testFilter() { Object o = runQuery( "select from reflect.classes as clazz " + "where clazz.getName().equals(\"java\" + \".lang.Object\")"); assertTrue(o instanceof Class[]); Class[] classes = (Class[]) o; assertEquals(1, classes.length); assertEquals(java.lang.Object.class, classes[0]); } public void testFilterNegative() { Object o = runQuery( "select from reflect.classes as clazz " + "where clazz.getName().equals(\"non.existent.class\")"); assertTrue(o instanceof Class[]); Class[] classes = (Class[]) o; assertEquals(0, classes.length); } public void _testJoin() { Object o = runQuery( "select from reflect.fields as field " + "where field.getClass() == java.lang.Object.class"); assertTrue(o instanceof Field[]); Field[] fields = (Field[]) o; assertEquals(10, fields.length); } // ------------------------------------------------------------------------ // classes follow /** * <code>JavaReflectConnection</code> is a connection to the virtual * database formed by the Java reflect API. */ public static class JavaReflectConnection implements Connection { private static JavaReflectSchema schema = new JavaReflectSchema(); public Schema getSchema() { return getSchemaStatic(); } public Object contentsAsArray(String tableName) { return null; } // for Connection public static Schema getSchemaStatic() { return schema; } } /** * <code>JavaReflectConnection</code> is the schema for the virtual * database formed by the Java reflect API. */ public static class JavaReflectSchema extends ReflectSchema { public Table fields = new ExtentTable(this, "fields", Field.class); public Table classes = new ExtentTable(this, "classes", Class.class); JavaReflectSchema() { } public void registerRules(Planner planner) throws Exception { addRelationship(planner, fields, classes, "getClass()", "getFields()"); // record that // select from classes as clazz // where clazz.getName().equals(<foo>) // can be transformed to // SaffronUtil.classesForName(<foo>) // which is almost the same as // select new Class[] {Class.forName(<foo>)} planner.addRule(new FilterRule( classes, new EqualsPattern( new MethodCall( RelEnvironment.makeReference(0), "getClass", null))) { public void onMatch(ParseTree[] tokens) { Expression expression = new MethodCall( Util.clazzSaffronUtil, "classesForName", new ExpressionList( (Expression) tokens[0])); Util.assert(call != null); call.transformTo( new ExpressionReader( call.rels[0].getCluster(), expression, null)); } }); } private void addRelationship( Planner planner, Table fromTable, Table toTable, String fromField, String toField) throws Exception { addLink(planner, fromTable, toTable, fromField, toField, false); addLink(planner, toTable, fromTable, toField, fromField, true); } private void addLink( Planner planner, Table fromTable, Table toTable, String fieldName, final String replaceFieldName, final boolean many) throws Exception { if (fieldName == null) { return; } OJClass fromClass = fromTable.getRowType(), toClass = toTable.getRowType(); Expression seek; if (fieldName.endsWith("()")) { String methodName = fieldName.substring( 0, fieldName.length() - 2); seek = new MethodCall( RelEnvironment.makeReference(0), methodName, null); } else { seek = new FieldAccess( RelEnvironment.makeReference(0), fieldName); } // In the many-to-one case, // // select // from fromTable // join toTable // on fromTable.method() == toTable // // e.g. // // select // from reflect.classes as clazz // join reflect.fields as field // on field.getClass() == clazz // // is equivalent to // // select {fromTable, x} // from fromTable.method() as x // // e.g. // // select {clazz, field} // from clazz.getFields() as field // // In the one-to-many case, // // select // from emp // join dept // on emp == dept.getEmp() // // is equivalent to // // select {dept.getEmp(), dept} // from dept // // because 'emp' is an extent, and therefore is guaranteed to // exist. (todo: Loosen up this rule, so body expressions don't // have to be tables.) planner.addRule( new JoinRule( fromTable, toTable, new EqualsPattern( seek)) { public void onMatch(ParseTree[] tokens) { Expression replaceExpr; if (replaceFieldName.endsWith("()")) { String methodName = replaceFieldName.substring( 0, replaceFieldName.length() - 2); replaceExpr = new MethodCall( RelEnvironment.makeReference(0), methodName, null); } else { replaceExpr = new FieldAccess( RelEnvironment.makeReference(0), replaceFieldName); } if (many) { throw Util.newInternal("todo:"); } else { Project project = new Project( call.rels[0].getCluster(), call.rels[2], new Expression[] { replaceExpr, RelEnvironment.makeReference(0)}, null, Project.flagBoxed); call.transformTo(project); } } }); } } /** * <code>ExtentTable</code> is a relational expression formed by all of * the instances of a given class. It is transformed into a {@link * ExtentRel}. * * todo: move this */ public static class ExtentTable extends AbstractTable { protected ExtentTable(Schema schema, String name, OJClass rowType) { super(schema, name, rowType); } protected ExtentTable(Schema schema, String name, Class rowType) { super(schema, name, rowType); } public Rel toRel(Cluster cluster, Expression schemaExp) { // ignore schemaExp -- we give the same results, regardless of // which 'connection' we have return new ExtentRel(cluster, getRowType(), this); } } /** * <code>ExtentRel</code> represents all of the instances of a particular * class (including subtypes). * * <p> It cannot be implemented as such, but can often be transformed into * an expression which can. For example, in<blockquote> * * <pre>JavaReflectConnection reflect; * select * from reflect.fields as field * join reflect.classes as clazz * on field.getClass() == clazz && * clazz.getPackage().getName().equals("java.lang");</pre> * * </blockquote>the <code>field</code> and <code>clazz</code> are * constrained by join and filter conditions so that we can enumerate * the required fields. * * <p> todo: move this * <p> todo: Why is this not just a TableAccess? **/ public static class ExtentRel extends Rel { private OJClass rowType; private Table table; // may be null, I guess public ExtentRel(Cluster cluster, OJClass rowType, Table table) { super(cluster); this.rowType = rowType; this.table = table; cluster.planner.registerSchema(table.getSchema()); } public Object clone() { return this; } protected OJClass deriveRowType() { return rowType; } protected Table getTable() { return table; } } /** * <code>FilterRule</code> is a template for a rule. It applies the * parse tree <code>pattern</code> to the filter condition, and each time * the pattern matches, fires the {@link onMatch(ParseTree[])} method * (which is abstract in this class). */ static abstract class FilterRule extends SingleConventionRule implements ParseTreeAction { ParseTreePattern pattern; Table table; RuleCall call; public FilterRule(Table table, ParseTreePattern pattern) { super( new Operand( Filter.class, // todo: constructor which takes a table new Operand[] { new Operand(Rel.class, null)}), CallingConvention.NONE); this.table = table; this.pattern = pattern; } public void onMatch(RuleCall call) { Filter filter = (Filter) call.rels[0]; Rel rel = call.rels[1]; if (!rel.isAccessTo(table)) { return; } Expression condition = filter.condition; this.call = call; // non-reentrant!! but okay pattern.match(condition, this); } } /** * <code>JoinRule</code> is a template for a rule. It applies the * parse tree <code>pattern</code> to the join condition, and each time * the pattern matches, fires the {@link onMatch(ParseTree[])} method * (which is abstract in this class). */ static abstract class JoinRule extends SingleConventionRule implements ParseTreeAction { ParseTreePattern pattern; Table fromTable; Table toTable; RuleCall call; public JoinRule(Table fromTable, Table toTable, ParseTreePattern pattern) { super( new Operand( Join.class, // todo: constructor which takes a table new Operand[] { new Operand(Rel.class, null), new Operand(Rel.class, null)}), CallingConvention.NONE); this.fromTable = fromTable; this.toTable = toTable; this.pattern = pattern; } public void onMatch(RuleCall call) { Join join = (Join) call.rels[0]; Rel left = call.rels[1], right = call.rels[2]; if (left.isAccessTo(fromTable) && right.isAccessTo(toTable)) { ; } else { return; } Expression condition = join.getCondition(); this.call = call; // non-reentrant!! but okay pattern.match(condition, this); } } /** * Matches <code>expr == $0</code> or * <code>expr.equals($0)</code> or * <code>$0.equals(expr)</code>. */ public static class EqualsPattern implements ParseTreePattern { Expression target; public EqualsPattern(Expression target) { this.target = target; } public void match(ParseTree ptree, ParseTreeAction action) { if (ptree instanceof BinaryExpression) { BinaryExpression binaryExpression = (BinaryExpression) ptree; if (binaryExpression.getOperator() == BinaryExpression.EQUAL) { Expression left = binaryExpression.getLeft(), right = binaryExpression.getRight(); if (left.equals(target)) { action.onMatch(new Expression[] {right}); } if (right.equals(target)) { action.onMatch(new Expression[] {left}); } } } else if (ptree instanceof MethodCall) { MethodCall methodCall = (MethodCall) ptree; if (methodCall.getName().equals("equals") && methodCall.getArguments().size() == 1) { Expression left = methodCall.getReferenceExpr(), right = methodCall.getArguments().get(0); if (left.equals(target)) { action.onMatch(new Expression[] {right}); } if (right.equals(target)) { action.onMatch(new Expression[] {left}); } } } } } } // End ObjectSchemaTest.java
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 1801 | Julian Hyde |
saffron: add ObjectSchema; rules can now be matched more than once; started to implement correlations in queries in from list. |