/* // $Id: //guest/julian_hyde/saffron/src/main/saffron/rel/SaffronQueryExpander.java#3 $ // 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. */ package saffron.rel; import openjava.mop.Environment; import openjava.mop.OJClass; import openjava.mop.QueryEnvironment; import openjava.mop.Toolbox; import openjava.ptree.*; import openjava.ptree.util.QueryExpander; import openjava.tools.DebugOut; import saffron.Planner; import saffron.Table; import saffron.opt.*; import saffron.rel.java.ForTerminator; import saffron.util.Util; /** * todo: * * @author jhyde * @since 22 September, 2001 * @version $Id: //guest/julian_hyde/saffron/src/main/saffron/rel/SaffronQueryExpander.java#3 $ **/ public class SaffronQueryExpander extends QueryExpander { public SaffronQueryExpander(Environment env) { super(env); } // implement QueryExpander protected Expression expandExpression(Expression exp) { while (true) { Expression exp0 = exp; exp = expandExpression_(exp0, null, CallingConvention.ARRAY); if (exp == exp0) { return exp; } } } /** * Converts an {@link Expression} into a java expression which returns an * array. Examples:<ol> * * <li>Query as expression * * <blockquote> * <pre>Emp[] femaleEmps = (select emp from emps as emp * where emp.gender.equals("F"));</pre> * </blockquote> * * becomes * * <blockquote> * <pre>Emp[] femaleEmps = new Object() { * public Emp[] asArray() { * Vector v = new Vector(); * for (i in (select emp from emps as emp * where emp.gender.equals("F"))) { * v.addElement(i); * } * Emp[] a = new Emp[v.size()]; * v.copyInto(a); * return a; * } * }.asArray();</pre> * </blockquote> * </li> * * </ul> * * @param desiredRowType row type required; if this is null, leave the * results as they are * @param convention calling convention of the result **/ private Expression expandExpression_( Expression exp, OJClass desiredRowType, int convention) { if (exp instanceof CastExpression) { // see if this is something like "(Emp[]) vector" or "(Emp[]) // connection.contentsAsArray("emps")" or "(Iterator) (select from // emps)" CastExpression castExp = (CastExpression) exp; Environment env = getEnvironment(); OJClass clazz = Util.getType(env, castExp); boolean retainCast; if (clazz.isArray()) { if (desiredRowType == null) { desiredRowType = clazz.getComponentType(); } } if (clazz.isArray()) { convention = CallingConvention.ARRAY; retainCast = false; } else if (clazz.isAssignableFrom(Toolbox.clazzIterable)) { // we prefer ITERABLE to ITERATOR convention = CallingConvention.ITERABLE; retainCast = false; } else if (clazz.isAssignableFrom(Toolbox.clazzIterator)) { convention = CallingConvention.ITERATOR; retainCast = false; } else if (clazz.isAssignableFrom(Toolbox.clazzCollection)) { convention = CallingConvention.COLLECTION; retainCast = false; } else if (clazz.isAssignableFrom(Toolbox.clazzVector)) { convention = CallingConvention.VECTOR; retainCast = false; } else if (clazz.isAssignableFrom(Toolbox.clazzEnumeration)) { convention = CallingConvention.ENUMERATION; retainCast = false; } else { retainCast = true; } Expression expanded = expandExpression_( castExp.getExpression(), desiredRowType, convention); if (retainCast) { expanded = new CastExpression( castExp.getTypeSpecifier(), expanded); } return expanded; } boolean eager = false; Rel rel = convertExpToRel(exp, eager, convention, null, null); if (rel != null) { return convertRelToExp(rel); } else if (exp instanceof BinaryExpression) { BinaryExpression binaryExp = (BinaryExpression) exp; if (binaryExp.getOperator() == BinaryExpression.IN) { // "in" outside a query context, e.g. "if (1 in new int[] // {1,2,3})" return expandIn(binaryExp.getLeft(), binaryExp.getRight()); } } else if (exp instanceof UnaryExpression) { UnaryExpression unaryExp = (UnaryExpression) exp; if (unaryExp.getOperator() == UnaryExpression.EXISTS) { // "exists" outside a query context, e.g. "if (exists (select // {} from emp))" return expandExists(unaryExp.getExpression()); } } return exp; } /** * Converts an {@link Expression} of the form <code>for (<i>variable</i> in * <i>exp</i>) { <i>body</i> }</code> into a regular java {@link * openjava.ptree.Statement}. **/ // implement QueryExpander protected Statement expandForIn( Variable variable, Expression exp, StatementList body) { boolean eager = true; int convention = CallingConvention.JAVA; Rel best = convertExpToRel(exp, eager, convention, variable, body); // spit out java statement block Implementor implementor = new Implementor(); Object o = implementor.implementRoot(best); return new Block((StatementList) o); } /** * Converts an expression to a relational expression according to a * particular calling convention, and optimizes it. * * @param rel relational expression to implement * @param convention calling convention (see {@link * saffron.opt.CallingConvention}) * @return the optimal relational expression **/ Rel convertExpToRel( Expression exp, boolean eager, int convention, Variable variable, StatementList body) { Rel rel = convertExpToRel(exp, eager, null, null); if (rel == null) { return null; } Planner planner = new VolcanoPlanner(rel.getCluster()); if (rel.getConvention() != convention) { Rel previous = rel; switch (convention) { case CallingConvention.JAVA: rel = planner.convert(rel, CallingConvention.JAVA); if (rel == null) { return null; } String label = Variable.generateUniqueVariable().toString(); rel = new ForTerminator( rel.getCluster(), rel, variable, body, label); break; default: rel = planner.convert(rel, convention); if (rel == null) { return null; } break; } DebugOut.println( "change convention: " + "rel#" + previous.getId() + ":" + CallingConvention.getName(previous.getConvention()) + " to " + "rel#" + rel.getId() + ":" + CallingConvention.getName(rel.getConvention())); Toolbox.assert(rel.getConvention() == convention); } planner.init(rel); planner = planner.chooseDelegate(); Rel bestExp = planner.findBestExp(); Toolbox.assert(bestExp != null, "could not implement exp"); return bestExp; } /** * Convert an expression to a relation, if it is relational, otherwise * return null. This method only deals with relational expressions which * can occur outside a query; joins, in particular, are only dealt with * inside queries. * * @param exp expression to translate * @param eager whether to translate expressions which may or may not be * (such as references to arrays) into relational expressions * @param queryInfo context to convert expressions into results of other * queries; null if this expression is not inside a query * @param desiredRowType row type required; if this is null, leave the * results as they are * @return a relational expression, or null if the expression was not * relational **/ Rel convertExpToRel( Expression exp, boolean eager, QueryInfo queryInfo, OJClass desiredRowType) { if (exp instanceof QueryExpression) { QueryExpression queryExp = (QueryExpression) exp; // Yeah, if we're being called from QueryExpander.evaluateDown we // will already have pushed a QueryEnvironment but another can't do // any harm QueryEnvironment qenv = new QueryEnvironment( getEnvironment(), queryExp); QueryInfo newQueryInfo = new QueryInfo( queryInfo, qenv, this, queryExp); return newQueryInfo.convertQueryToRel(queryExp); } if (exp instanceof BinaryExpression) { BinaryExpression binaryExp = (BinaryExpression) exp; switch (binaryExp.getOperator()) { case BinaryExpression.UNION: { Expression leftExp = binaryExp.getLeft(), rightExp = binaryExp.getRight(); Rel left = convertExpToRel(leftExp, true, queryInfo, null); Rel right = convertExpToRel(rightExp, true, queryInfo, null); Query query = queryInfo == null ? new Query() : queryInfo.cluster.query; Cluster cluster = new Cluster(query, getEnvironment(), exp); return new Union(cluster, new Rel[] {left, right}, false); } case BinaryExpression.REL_MINUS: { Expression leftExp = binaryExp.getLeft(), rightExp = binaryExp.getRight(); Rel left = convertExpToRel(leftExp, true, queryInfo, null); Rel right = convertExpToRel(rightExp, true, queryInfo, null); Query query = queryInfo == null ? new Query() : queryInfo.cluster.query; Cluster cluster = new Cluster(query, getEnvironment(), exp); return new Minus(cluster, left, right); } case BinaryExpression.INTERSECT: { Expression leftExp = binaryExp.getLeft(), rightExp = binaryExp.getRight(); Rel left = convertExpToRel(leftExp, true, queryInfo, null); Rel right = convertExpToRel(rightExp, true, queryInfo, null); Query query = queryInfo == null ? new Query() : queryInfo.cluster.query; Cluster cluster = new Cluster(query, getEnvironment(), exp); return new Intersect(cluster, left, right); } default: return null; } } // We only translate arrays, vectors, collections into relational // expressions if they occur with a select statement. if (!eager) { return null; } tryit: if (exp instanceof MethodCall) { MethodCall call = (MethodCall) exp; String name = call.getName(); if (!name.equals("contentsAsArray")) { break tryit; } Expression refexpr = call.getReferenceExpr(); if (refexpr == null) { break tryit; } ExpressionList args = call.getArguments(); if (args.size() != 1) { break tryit; } Expression arg = args.get(0); if (!(arg instanceof Literal)) { break tryit; } Literal literal = (Literal) arg; if (literal.getLiteralType() != Literal.STRING) { break tryit; } String tableName = Literal.stripString(literal.toString()); Environment env = getEnvironment(); OJClass reftype = Util.getType(env, refexpr); Table table = Toolbox.getTable(reftype, tableName); if (table == null) { break tryit; } Rel rel = table.toRel(queryInfo.cluster, refexpr); if (rel == null) { return null; } queryInfo.leaves.add(rel); return rel; } OJClass clazz = Util.getType(getEnvironment(), exp); // An array cast is not meant literally -- it tells us what the row // type is. if (exp instanceof CastExpression) { CastExpression castExp = (CastExpression) exp; if (clazz.isArray() && desiredRowType == null) { desiredRowType = clazz.getComponentType(); return convertExpToRel( castExp.getExpression(), eager, queryInfo, desiredRowType); } } if (ExpressionReader.isRelational(clazz)) { Cluster cluster; if (queryInfo != null) { exp = queryInfo.convertExpToInternal(exp, new Rel[]{}); cluster = queryInfo.cluster; } else { // there's no query current, but we still need a cluster cluster = new Cluster(new Query(), getEnvironment(), exp); } Rel rel = new ExpressionReader(cluster, exp, desiredRowType); if (queryInfo != null) { queryInfo.leaves.add(rel); } return rel; } throw Toolbox.newInternal("exp is not relational: " + exp); } /** * Translates an expression involving <code>in</code>. For example, * * <blockquote> * <pre>if ("Fred" in (select name from emps as emp)) { * yes(); * }</pre> * <blockquote> * * becomes <code>in</code> within a query * * <blockquote> * <pre>if (exists ( * select where "Joe" in (select name from emps as emp))) { * yes(); * }</pre> * </blockquote> **/ private Expression expandIn(Expression seekExp, Expression queryExp) { return expandExists( new QueryExpression( null, true, null, null, new BinaryExpression( seekExp, BinaryExpression.IN, queryExp), null)); } /** * Translates an expression containing <code>exists</code> into one not * using <code>exists</code>. It may contain <code>for (... in * ...)</code>, which needs to be translated further. * * <p>Example: * * <blockquote> * <pre>if (exists (select from emps where emp.gender.equals("F"))) { * yes(); * }</pre> * </blockquote> * * becomes * * <blockquote> * <pre>if (new Object() { * public boolean anyRows() { * for (i in (select emp from emps as emp * where emp.gender.equals("F"))) { * return true; * } * return false; * } * }.anyRows()) { * yes(); * }</pre> * </blockquote> **/ private Expression expandExists_obsolete(Expression exp) { Variable var_i = Variable.generateUniqueVariable(); return new MethodCall( new AllocationExpression( new TypeName("Object"), // "Object" null, // "()" // "public boolean anyRows() { ... }" new MemberDeclarationList( new MethodDeclaration( new ModifierList(), new TypeName("boolean"), // "boolean" "anyRows", null, // "()" null, // throws nothing new StatementList( new ForStatement( var_i.toString(), exp, new StatementList( new ReturnStatement( Literal.constantTrue()))), new ReturnStatement( Literal.constantFalse()))))), "anyRows", null); } private Expression expandExists(Expression exp) { boolean eager = true; int convention = CallingConvention.EXISTS; Rel rel = convertExpToRel(exp, eager, convention, null, null); return convertRelToExp(rel); } private Expression convertRelToExp(Rel rel) { // spit out java statement block Implementor implementor = new Implementor(); Object o = implementor.implementRoot(rel); return (Expression) o; } } // End SaffronQueryExpander.java
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#4 | 1801 | Julian Hyde |
saffron: add ObjectSchema; rules can now be matched more than once; started to implement correlations in queries in from list. |
||
#3 | 1748 | Julian Hyde |
saffron: convert unit tests to JUnit; add CallingConvention.ITERABLE; lots of other stuff; release 0.1. |
||
#2 | 1474 | Julian Hyde |
saffron: Aggregations are working. Renamed 'aggregator' to 'aggregation'. |
||
#1 | 1467 | Julian Hyde |
saffron: First saffron check-in; incorporate my changes to openjava. |