/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.ejb.plugins.cmp.jdbc; import java.io.StringReader; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.jboss.ejb.plugins.cmp.ejbql.ASTAbs; import org.jboss.ejb.plugins.cmp.ejbql.ASTAbstractSchema; import org.jboss.ejb.plugins.cmp.ejbql.ASTBooleanLiteral; import org.jboss.ejb.plugins.cmp.ejbql.ASTCollectionMemberDeclaration; import org.jboss.ejb.plugins.cmp.ejbql.ASTConcat; import org.jboss.ejb.plugins.cmp.ejbql.ASTEJBQL; import org.jboss.ejb.plugins.cmp.ejbql.ASTEntityComparison; import org.jboss.ejb.plugins.cmp.ejbql.ASTFrom; import org.jboss.ejb.plugins.cmp.ejbql.ASTIdentifier; import org.jboss.ejb.plugins.cmp.ejbql.ASTIsEmpty; import org.jboss.ejb.plugins.cmp.ejbql.ASTLCase; import org.jboss.ejb.plugins.cmp.ejbql.ASTLength; import org.jboss.ejb.plugins.cmp.ejbql.ASTLocate; import org.jboss.ejb.plugins.cmp.ejbql.ASTMemberOf; import org.jboss.ejb.plugins.cmp.ejbql.ASTNullComparison; import org.jboss.ejb.plugins.cmp.ejbql.ASTOrderBy; import org.jboss.ejb.plugins.cmp.ejbql.ASTParameter; import org.jboss.ejb.plugins.cmp.ejbql.ASTPath; import org.jboss.ejb.plugins.cmp.ejbql.ASTRangeVariableDeclaration; import org.jboss.ejb.plugins.cmp.ejbql.ASTSelect; import org.jboss.ejb.plugins.cmp.ejbql.ASTSqrt; import org.jboss.ejb.plugins.cmp.ejbql.ASTSubstring; import org.jboss.ejb.plugins.cmp.ejbql.ASTUCase; import org.jboss.ejb.plugins.cmp.ejbql.ASTValueClassComparison; import org.jboss.ejb.plugins.cmp.ejbql.ASTWhere; import org.jboss.ejb.plugins.cmp.ejbql.BasicVisitor; import org.jboss.ejb.plugins.cmp.ejbql.Catalog; import org.jboss.ejb.plugins.cmp.ejbql.EJBQLParser; import org.jboss.ejb.plugins.cmp.ejbql.EJBQLTypes; import org.jboss.ejb.plugins.cmp.ejbql.JBossQLParser; import org.jboss.ejb.plugins.cmp.ejbql.Node; import org.jboss.ejb.plugins.cmp.ejbql.SimpleNode; import org.jboss.ejb.plugins.cmp.ejbql.ASTLimitOffset; import org.jboss.ejb.plugins.cmp.ejbql.ASTCount; import org.jboss.ejb.plugins.cmp.ejbql.SelectFunction; import org.jboss.ejb.plugins.cmp.ejbql.ASTExactNumericLiteral; import org.jboss.ejb.plugins.cmp.ejbql.ASTMax; import org.jboss.ejb.plugins.cmp.ejbql.ASTMin; import org.jboss.ejb.plugins.cmp.ejbql.ASTAvg; import org.jboss.ejb.plugins.cmp.ejbql.ASTSum; import org.jboss.ejb.plugins.cmp.ejbql.ASTWhereConditionalTerm; import org.jboss.ejb.plugins.cmp.ejbql.ASTMod; import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMPFieldBridge; import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMRFieldBridge; import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge; import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCFieldBridge; import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCAbstractEntityBridge; import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCFunctionMappingMetaData; import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCReadAheadMetaData; import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCTypeMappingMetaData; import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCQueryMetaData; import org.jboss.ejb.plugins.cmp.bridge.CMPFieldBridge; import org.jboss.ejb.EntityPersistenceStore; import org.jboss.deployment.DeploymentException; /** * Compiles EJB-QL and JBossQL into SQL. * * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a> * @author <a href="mailto:alex@jboss.org">Alex Loubyansky</a> * @version $Revision: 106022 $ */ public final class JDBCEJBQLCompiler extends BasicVisitor implements QLCompiler { // input objects private final Catalog catalog; private Class returnType; private Class[] parameterTypes; private JDBCReadAheadMetaData readAhead; private boolean lazyResultSetLoading; // alias info private AliasManager aliasManager; // join info private Set declaredPaths = new HashSet(); private Set ctermJoinPaths = new HashSet(); private Set allJoinPaths = new HashSet(); private Map ctermCollectionMemberJoinPaths = new HashMap(); private Map allCollectionMemberJoinPaths = new HashMap(); private Map ctermLeftJoinPaths = new HashMap(); private Map allLeftJoinPaths = new HashMap(); // mapping metadata private JDBCTypeMappingMetaData typeMapping; private JDBCTypeFactory typeFactory; private boolean subquerySupported; // output objects private boolean forceDistinct; private String sql; private int offsetParam; private int offsetValue; private int limitParam; private int limitValue; private JDBCStoreManager selectManager; private Object selectObject; private List inputParameters = new ArrayList(); private JDBCType functionJDBCType; /** * deep read ahead for cmrs */ private List leftJoinCMRList = new ArrayList(); private StringBuffer onFindCMRJoin; private boolean countCompositePk; private String selectAlias; private boolean selectDistinct; public JDBCEJBQLCompiler(Catalog catalog) { this.catalog = catalog; } public void compileEJBQL(String ejbql, Class returnType, Class[] parameterTypes, JDBCQueryMetaData metadata) throws Exception { // reset all state variables reset(); // set input arguemts this.returnType = returnType; this.parameterTypes = parameterTypes; this.readAhead = metadata.getReadAhead(); this.lazyResultSetLoading = metadata.isLazyResultSetLoading(); // get the parser EJBQLParser parser = new EJBQLParser(new StringReader(SQLUtil.EMPTY_STRING)); try { // parse the ejbql into an abstract sytax tree ASTEJBQL ejbqlNode; ejbqlNode = parser.parse(catalog, parameterTypes, ejbql); // translate to sql sql = ejbqlNode.jjtAccept(this, new StringBuffer()).toString(); } catch(Exception e) { // if there is a problem reset the state before exiting reset(); throw e; } catch(Error e) { // lame javacc lexer throws Errors reset(); throw e; } } public void compileJBossQL(String ejbql, Class returnType, Class[] parameterTypes, JDBCQueryMetaData metadata) throws Exception { // reset all state variables reset(); // set input arguemts this.returnType = returnType; this.parameterTypes = parameterTypes; this.readAhead = metadata.getReadAhead(); this.lazyResultSetLoading = metadata.isLazyResultSetLoading(); // get the parser JBossQLParser parser = new JBossQLParser(new StringReader(SQLUtil.EMPTY_STRING)); try { // parse the ejbql into an abstract sytax tree ASTEJBQL ejbqlNode; ejbqlNode = parser.parse(catalog, parameterTypes, ejbql); // translate to sql sql = ejbqlNode.jjtAccept(this, new StringBuffer()).toString(); } catch(Exception e) { // if there is a problem reset the state before exiting reset(); throw e; } catch(Error e) { // lame javacc lexer throws Errors reset(); throw e; } } private void reset() { returnType = null; parameterTypes = null; readAhead = null; inputParameters.clear(); declaredPaths.clear(); clearPerTermJoinPaths(); allJoinPaths.clear(); allCollectionMemberJoinPaths.clear(); allLeftJoinPaths.clear(); selectObject = null; selectManager = null; typeFactory = null; typeMapping = null; aliasManager = null; subquerySupported = true; forceDistinct = false; limitParam = 0; limitValue = 0; offsetParam = 0; offsetValue = 0; leftJoinCMRList.clear(); onFindCMRJoin = null; countCompositePk = false; selectAlias = null; selectDistinct = false; functionJDBCType = null; } public String getSQL() { return sql; } public int getOffsetValue() { return offsetValue; } public int getOffsetParam() { return offsetParam; } public int getLimitValue() { return limitValue; } public int getLimitParam() { return limitParam; } public boolean isSelectEntity() { return selectObject instanceof JDBCEntityBridge; } public JDBCAbstractEntityBridge getSelectEntity() { return (JDBCAbstractEntityBridge) selectObject; } public boolean isSelectField() { return selectObject instanceof JDBCCMPFieldBridge; } public JDBCFieldBridge getSelectField() { return (JDBCCMPFieldBridge) selectObject; } public SelectFunction getSelectFunction() { return (SelectFunction) selectObject; } public EntityPersistenceStore getStoreManager() { return selectManager; } public List getInputParameters() { return inputParameters; } public List getLeftJoinCMRList() { return leftJoinCMRList; } public boolean isSelectDistinct() { return selectDistinct; } public Object visit(SimpleNode node, Object data) { throw new RuntimeException("Internal error: Found unknown node type in " + "EJB-QL abstract syntax tree: node=" + node); } private void setTypeFactory(JDBCTypeFactory typeFactory) { this.typeFactory = typeFactory; this.typeMapping = typeFactory.getTypeMapping(); aliasManager = new AliasManager(typeMapping.getAliasHeaderPrefix(), typeMapping.getAliasHeaderSuffix(), typeMapping.getAliasMaxLength()); subquerySupported = typeMapping.isSubquerySupported(); } private Class getParameterType(int index) { int zeroBasedIndex = index - 1; Class[] params = parameterTypes; if(zeroBasedIndex < params.length) { return params[zeroBasedIndex]; } return null; } // verify that parameter is the same type as the entity private void verifyParameterEntityType(int number, JDBCEntityBridge entity) { Class parameterType = getParameterType(number); Class remoteClass = entity.getMetaData().getRemoteClass(); Class localClass = entity.getMetaData().getLocalClass(); if(( localClass == null || !localClass.isAssignableFrom(parameterType) ) && ( remoteClass == null || !remoteClass.isAssignableFrom(parameterType) )) { throw new IllegalStateException("Only like types can be " + "compared: from entity=" + entity.getEntityName() + " to parameter type=" + parameterType); } } private void compareEntity(boolean not, Node fromNode, Node toNode, StringBuffer buf) { buf.append('('); if(not) { buf.append(SQLUtil.NOT).append('('); } String fromAlias; JDBCEntityBridge fromEntity; ASTPath fromPath = (ASTPath) fromNode; addJoinPath(fromPath); fromAlias = aliasManager.getAlias(fromPath.getPath()); fromEntity = (JDBCEntityBridge) fromPath.getEntity(); if(toNode instanceof ASTParameter) { ASTParameter toParam = (ASTParameter) toNode; // can only compare like kind entities verifyParameterEntityType(toParam.number, fromEntity); inputParameters.addAll(QueryParameter.createParameters(toParam.number - 1, fromEntity)); SQLUtil.getWhereClause(fromEntity.getPrimaryKeyFields(), fromAlias, buf); } else { String toAlias; JDBCEntityBridge toEntity; ASTPath toPath = (ASTPath) toNode; addJoinPath(toPath); toAlias = aliasManager.getAlias(toPath.getPath()); toEntity = (JDBCEntityBridge) toPath.getEntity(); // can only compare like kind entities if(!fromEntity.equals(toEntity)) { throw new IllegalStateException("Only like types can be " + "compared: from entity=" + fromEntity.getEntityName() + " to entity=" + toEntity.getEntityName()); } SQLUtil.getSelfCompareWhereClause(fromEntity.getPrimaryKeyFields(), fromAlias, toAlias, buf); } if(not) { buf.append(')'); } buf.append(')'); } private void existsClause(ASTPath path, StringBuffer buf, boolean not) { if(!path.isCMRField()) { throw new IllegalArgumentException("path must be a cmr field"); } JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) path.getCMRField(); String pathStr = path.getPath(path.size() - 2); String parentAlias = aliasManager.getAlias(pathStr); // if exists is not supported we use a left join and is null if(!subquerySupported) { // add the path to the list of paths to left join addLeftJoinPath(pathStr, path); forceDistinct = true; addJoinPath(path); if(cmrField.getRelationMetaData().isForeignKeyMappingStyle()) { JDBCEntityBridge childEntity = (JDBCEntityBridge) cmrField.getRelatedEntity(); String childAlias = aliasManager.getAlias(path.getPath()); SQLUtil.getIsNullClause(!not, childEntity.getPrimaryKeyFields(), childAlias, buf); } else { String relationTableAlias = aliasManager.getRelationTableAlias(path.getPath()); SQLUtil.getIsNullClause(!not, cmrField.getTableKeyFields(), relationTableAlias, buf); } return; } if(not) { buf.append(SQLUtil.NOT); } buf.append(SQLUtil.EXISTS).append('('); if(cmrField.getRelationMetaData().isForeignKeyMappingStyle()) { JDBCEntityBridge childEntity = (JDBCEntityBridge) cmrField.getRelatedEntity(); String childAlias = aliasManager.getAlias(path.getPath()); buf.append(SQLUtil.SELECT); SQLUtil.getColumnNamesClause(childEntity.getPrimaryKeyFields(), childAlias, buf) .append(SQLUtil.FROM) .append(childEntity.getQualifiedTableName()).append(' ').append(childAlias) .append(SQLUtil.WHERE); SQLUtil.getJoinClause(cmrField, parentAlias, childAlias, buf); } else { String relationTableAlias = aliasManager.getRelationTableAlias(path.getPath()); buf.append(SQLUtil.SELECT); SQLUtil.getColumnNamesClause(cmrField.getTableKeyFields(), relationTableAlias, buf) .append(SQLUtil.FROM) .append(cmrField.getQualifiedTableName()) .append(' ') .append(relationTableAlias) .append(SQLUtil.WHERE); SQLUtil.getRelationTableJoinClause(cmrField, parentAlias, relationTableAlias, buf); } buf.append(')'); } public Object visit(ASTEJBQL node, Object data) { Node selectNode = node.jjtGetChild(0); Node fromNode = node.jjtGetChild(1); Node whereNode = null; Node orderByNode = null; Node limitNode = null; for(int childNode = 2; childNode < node.jjtGetNumChildren(); childNode++) { Node temp = node.jjtGetChild(childNode); if(temp instanceof ASTWhere) { whereNode = temp; } else if(temp instanceof ASTOrderBy) { orderByNode = temp; } else if(temp instanceof ASTLimitOffset) { limitNode = temp; } } // translate select and add it to the buffer StringBuffer select = new StringBuffer(); selectNode.jjtAccept(this, select); // conditional term paths from the where clause are treated separately from those // in select, from and order by. // TODO come up with a nicer treatment implementation. Set selectJoinPaths = new HashSet(ctermJoinPaths); Map selectCollectionMemberJoinPaths = new HashMap(ctermCollectionMemberJoinPaths); Map selectLeftJoinPaths = new HashMap(ctermLeftJoinPaths); // translate where and save results to append later StringBuffer where = new StringBuffer(); if(whereNode != null) { whereNode.jjtAccept(this, where); } // reassign conditional term paths and add paths from order by and from to select paths ctermJoinPaths = selectJoinPaths; ctermCollectionMemberJoinPaths = selectCollectionMemberJoinPaths; ctermLeftJoinPaths = selectLeftJoinPaths; // translate order by and save results to append later StringBuffer orderBy = new StringBuffer(); if(orderByNode != null) { orderByNode.jjtAccept(this, orderBy); // hack alert - this should use the visitor approach for(int i = 0; i < orderByNode.jjtGetNumChildren(); i++) { Node orderByPath = orderByNode.jjtGetChild(i); ASTPath path = (ASTPath) orderByPath.jjtGetChild(0); if(!isSelected(path)) { select.append(SQLUtil.COMMA); path.jjtAccept(this, select); } } } if(limitNode != null) { limitNode.jjtAccept(this, null); } StringBuffer from = new StringBuffer(50); fromNode.jjtAccept(this, from); StringBuffer fromThetaJoin = new StringBuffer(); createThetaJoin(fromThetaJoin); if(where.length() != 0 && fromThetaJoin.length() != 0) { where.insert(0, '(') .append(')') .append(SQLUtil.AND) .append(fromThetaJoin); } else if(fromThetaJoin.length() != 0) { where.append(fromThetaJoin.toString()); } selectDistinct = isDistinct(selectNode); // select size if(lazyResultSetLoading) { StringBuffer buf = new StringBuffer(200); if(isSelectEntity()) { final JDBCFieldBridge[] pkFields = getSelectEntity().getPrimaryKeyFields(); if(pkFields.length == 1) { buf.append('(').append(SQLUtil.SELECT).append("count("); if(selectDistinct) { buf.append(SQLUtil.DISTINCT); } SQLUtil.getColumnNamesClause(pkFields, selectAlias, buf); buf.append(')').append(SQLUtil.FROM); buf.append(from); if(where.length() > 0) { buf.append(SQLUtil.WHERE).append(where); } buf.append("), "); select.insert(0, buf); } else { buf.append('(').append(SQLUtil.SELECT).append("count(*)").append(SQLUtil.FROM) .append('(') .append(SQLUtil.SELECT); if(selectDistinct) { buf.append(SQLUtil.DISTINCT); } SQLUtil.getColumnNamesClause(pkFields, selectAlias, buf); buf.append(SQLUtil.FROM).append(from); if(where.length() > 0) { buf.append(SQLUtil.WHERE).append(where); } buf.append(") t_count), "); select.insert(0, buf); } } else if(isSelectField()) { buf.append('(').append(SQLUtil.SELECT).append("count("); if(selectDistinct) { buf.append(SQLUtil.DISTINCT); } buf.append(select).append(')').append(SQLUtil.FROM); buf.append(from); if(where.length() > 0) { buf.append(SQLUtil.WHERE).append(where); } buf.append("), "); select.insert(0, buf); } } // distinct if(selectDistinct) { select.insert(0, SQLUtil.DISTINCT); } StringBuffer buf = (StringBuffer) data; if(selectManager.getMetaData().hasRowLocking()) { JDBCFunctionMappingMetaData rowLockingTemplate = typeMapping.getRowLockingTemplate(); Object args[] = new Object[]{ select, from, where.length() == 0 ? null : where, orderBy.length() == 0 ? null : orderBy }; rowLockingTemplate.getFunctionSql(args, buf); } else { buf.append(SQLUtil.SELECT) .append(select) .append(SQLUtil.FROM) .append(from); if(where.length() > 0) { buf.append(SQLUtil.WHERE) .append(where); } if(orderBy.length() != 0) { buf.append(SQLUtil.ORDERBY) .append(orderBy); } } // todo: ... if(countCompositePk) { buf.insert(0, "SELECT COUNT(*) FROM (").append(") t_count"); } return buf; } public Object visit(ASTFrom node, Object data) { StringBuffer buf = (StringBuffer) data; node.jjtGetChild(0).jjtAccept(this, buf); for(int i = 1; i < node.jjtGetNumChildren(); i++) { buf.append(SQLUtil.COMMA); node.jjtGetChild(i).jjtAccept(this, buf); } // add all the additional path tables if(!allJoinPaths.isEmpty()) { for(Iterator iter = allJoinPaths.iterator(); iter.hasNext();) { ASTPath path = (ASTPath) iter.next(); for(int i = 0; i < path.size(); i++) { declareTables(path, i, buf); } } } // add all parent paths for collection member join paths if(!allCollectionMemberJoinPaths.isEmpty()) { for(Iterator iter = allCollectionMemberJoinPaths.values().iterator(); iter.hasNext();) { ASTPath path = (ASTPath) iter.next(); // don't declare the last one as the first path was left joined for(int i = 0; i < path.size() - 1; i++) { declareTables(path, i, buf); } } } // get all the left joined paths if(!allLeftJoinPaths.isEmpty()) { Set allLeftJoins = new HashSet(); for(Iterator iter = allLeftJoinPaths.values().iterator(); iter.hasNext();) { allLeftJoins.addAll((Set) iter.next()); } // add all parent paths for left joins for(Iterator iter = allLeftJoins.iterator(); iter.hasNext();) { ASTPath path = (ASTPath) iter.next(); // don't declare the last one as the first path was left joined for(int i = 0; i < path.size() - 1; i++) { declareTables(path, i, buf); } } } return buf; } private void declareTables(ASTPath path, int i, StringBuffer buf) { if(!path.isCMRField(i) || declaredPaths.contains(path.getPath(i))) { return; } JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) path.getCMRField(i); JDBCEntityBridge entity = (JDBCEntityBridge) path.getEntity(i); buf.append(SQLUtil.COMMA) .append(entity.getQualifiedTableName()) .append(' ') .append(aliasManager.getAlias(path.getPath(i))); leftJoins(path.getPath(i), buf); if(cmrField.getRelationMetaData().isTableMappingStyle()) { String relationTableAlias = aliasManager.getRelationTableAlias(path.getPath(i)); buf.append(SQLUtil.COMMA) .append(cmrField.getQualifiedTableName()) .append(' ') .append(relationTableAlias); } declaredPaths.add(path.getPath(i)); } private void leftJoins(String parentPath, StringBuffer buf) { Set paths = (Set) ctermLeftJoinPaths.get(parentPath); if(subquerySupported || paths == null) { return; } for(Iterator iter = paths.iterator(); iter.hasNext();) { ASTPath path = (ASTPath) iter.next(); JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) path.getCMRField(); String parentAlias = aliasManager.getAlias(parentPath); if(cmrField.getRelationMetaData().isForeignKeyMappingStyle()) { JDBCEntityBridge childEntity = (JDBCEntityBridge) cmrField.getRelatedEntity(); String childAlias = aliasManager.getAlias(path.getPath()); buf.append(SQLUtil.LEFT_JOIN) .append(childEntity.getQualifiedTableName()) .append(' ') .append(childAlias) .append(SQLUtil.ON); SQLUtil.getJoinClause(cmrField, parentAlias, childAlias, buf); } else { String relationTableAlias = aliasManager.getRelationTableAlias(path.getPath()); buf.append(SQLUtil.LEFT_JOIN) .append(cmrField.getQualifiedTableName()) .append(' ') .append(relationTableAlias) .append(SQLUtil.ON); SQLUtil.getRelationTableJoinClause(cmrField, parentAlias, relationTableAlias, buf); } } } private void createThetaJoin(StringBuffer buf) { Set joinedAliases = new HashSet(); // add all the additional path tables if(!ctermJoinPaths.isEmpty()) { for(Iterator iter = ctermJoinPaths.iterator(); iter.hasNext();) { ASTPath path = (ASTPath) iter.next(); for(int i = 0; i < path.size(); i++) { createThetaJoin(path, i, joinedAliases, buf); } } } // add all the collection member path tables if(!ctermCollectionMemberJoinPaths.isEmpty()) { for(Iterator iter = ctermCollectionMemberJoinPaths.entrySet().iterator(); iter.hasNext();) { Map.Entry entry = (Map.Entry) iter.next(); String childAlias = (String) entry.getKey(); ASTPath path = (ASTPath) entry.getValue(); // join the memeber path createThetaJoin(path, path.size() - 1, joinedAliases, childAlias, buf); // join the memeber path parents for(int i = 0; i < path.size() - 1; i++) { createThetaJoin(path, i, joinedAliases, buf); } } } // get all the left joined paths if(!ctermLeftJoinPaths.isEmpty()) { Set allLeftJoins = new HashSet(); for(Iterator iter = ctermLeftJoinPaths.values().iterator(); iter.hasNext();) { allLeftJoins.addAll((Set) iter.next()); } // add all parent paths for left joins for(Iterator iter = allLeftJoins.iterator(); iter.hasNext();) { ASTPath path = (ASTPath) iter.next(); // don't declare the last one as the first path was left joined for(int i = 0; i < path.size() - 1; i++) { createThetaJoin(path, i, joinedAliases, buf); } } } } private void createThetaJoin(ASTPath path, int i, Set joinedAliases, StringBuffer buf) { String childAlias = aliasManager.getAlias(path.getPath(i)); createThetaJoin(path, i, joinedAliases, childAlias, buf); } private void createThetaJoin(ASTPath path, int i, Set joinedAliases, String childAlias, StringBuffer buf) { if(!path.isCMRField(i) || joinedAliases.contains(childAlias)) { return; } JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) path.getCMRField(i); String parentAlias = aliasManager.getAlias(path.getPath(i - 1)); if(joinedAliases.size() > 0) { buf.append(SQLUtil.AND); } if(cmrField.getRelationMetaData().isForeignKeyMappingStyle()) { SQLUtil.getJoinClause(cmrField, parentAlias, childAlias, buf); } else { String relationTableAlias = aliasManager.getRelationTableAlias(path.getPath(i)); // parent to relation table SQLUtil.getRelationTableJoinClause(cmrField, parentAlias, relationTableAlias, buf) .append(SQLUtil.AND); // child to relation table SQLUtil.getRelationTableJoinClause(cmrField.getRelatedCMRField(), childAlias, relationTableAlias, buf); } joinedAliases.add(childAlias); } public Object visit(ASTCollectionMemberDeclaration node, Object data) { StringBuffer buf = (StringBuffer) data; // first arg is a collection valued path ASTPath path = (ASTPath) node.jjtGetChild(0); // add this path to the list of declared paths declaredPaths.add(path.getPath()); // get the entity at the end of this path JDBCEntityBridge entity = (JDBCEntityBridge) path.getEntity(); // second arg is the identifier ASTIdentifier id = (ASTIdentifier) node.jjtGetChild(1); // get the alias String alias = aliasManager.getAlias(id.identifier); // add this path to the list of join paths so parent paths will be joined addCollectionMemberJoinPath(alias, path); // declare the alias mapping aliasManager.addAlias(path.getPath(), alias); buf.append(entity.getQualifiedTableName()); buf.append(' '); buf.append(alias); leftJoins(path.getPath(), buf); if(onFindCMRJoin != null && alias.equals(selectAlias)) { buf.append(onFindCMRJoin); onFindCMRJoin = null; } // add the relation-table JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) path.getCMRField(); if(cmrField.getRelationMetaData().isTableMappingStyle()) { String relationTableAlias = aliasManager.getRelationTableAlias(path.getPath()); buf.append(SQLUtil.COMMA) .append(cmrField.getQualifiedTableName()) .append(' ') .append(relationTableAlias); } return buf; } public Object visit(ASTRangeVariableDeclaration node, Object data) { StringBuffer buf = (StringBuffer) data; ASTAbstractSchema schema = (ASTAbstractSchema) node.jjtGetChild(0); JDBCEntityBridge entity = (JDBCEntityBridge) schema.entity; ASTIdentifier id = (ASTIdentifier) node.jjtGetChild(1); String alias = aliasManager.getAlias(id.identifier); buf.append(entity.getQualifiedTableName()) .append(' ') .append(alias); leftJoins(id.identifier, buf); if(onFindCMRJoin != null && alias.equals(selectAlias)) { buf.append(onFindCMRJoin); onFindCMRJoin = null; } return buf; } public Object visit(ASTSelect node, Object data) { StringBuffer buf = (StringBuffer) data; Node child0 = node.jjtGetChild(0); ASTPath path; if(child0 instanceof ASTPath) { path = (ASTPath) child0; if(path.isCMPField()) { // set the select object JDBCCMPFieldBridge selectField = (JDBCCMPFieldBridge) path.getCMPField(); selectManager = (JDBCStoreManager) selectField.getManager(); selectObject = selectField; setTypeFactory(selectManager.getJDBCTypeFactory()); addJoinPath(path); selectAlias = aliasManager.getAlias(path.getPath(path.size() - 2)); SQLUtil.getColumnNamesClause(selectField, selectAlias, buf); } else { // set the select object JDBCEntityBridge selectEntity = (JDBCEntityBridge) path.getEntity(); selectManager = (JDBCStoreManager) selectEntity.getManager(); selectObject = selectEntity; setTypeFactory(selectManager.getJDBCTypeFactory()); selectEntity(path, node.distinct, buf); } } else { // the function should take a path expresion as a parameter path = getPathFromChildren(child0); if(path == null) { throw new IllegalStateException("The function in SELECT clause does not contain a path expression."); } if(path.isCMPField()) { JDBCCMPFieldBridge selectField = (JDBCCMPFieldBridge) path.getCMPField(); selectManager = (JDBCStoreManager) selectField.getManager(); if(selectField.getJDBCType().hasMapper()) this.functionJDBCType = selectField.getJDBCType(); } else if(path.isCMRField()) { JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) path.getCMRField(); selectManager = (JDBCStoreManager) cmrField.getEntity().getManager(); addJoinPath(path); } else { final JDBCEntityBridge entity = (JDBCEntityBridge) path.getEntity(); selectManager = (JDBCStoreManager) entity.getManager(); addJoinPath(path); } setTypeFactory(selectManager.getJDBCTypeFactory()); selectObject = child0; child0.jjtAccept(this, buf); } return buf; } /** * Generates where clause without the "WHERE" keyword. */ public Object visit(ASTWhere node, Object data) { node.jjtGetChild(0).jjtAccept(this, data); return data; } public Object visit(ASTNullComparison node, Object data) { StringBuffer buf = (StringBuffer) data; final Node child0 = node.jjtGetChild(0); if(child0 instanceof ASTPath) { ASTPath path = (ASTPath) child0; if(path.isCMRField()) { JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) path.getCMRField(); if(cmrField.getRelationMetaData().isTableMappingStyle()) { existsClause(path, buf, !node.not); return buf; } } String alias = aliasManager.getAlias(path.getPath(path.size() - 2)); JDBCFieldBridge field = (JDBCFieldBridge) path.getField(); // if jdbc type is null then it should be a cmr field in // a one-to-one mapping that isn't a foreign key. // handle it the way the IS EMPTY on the one side of one-to-many // relationship is handled if(field.getJDBCType() == null) { existsClause(path, buf, !node.not); return buf; } // check the path for cmr fields and add them to join paths if(path.fieldList.size() > 2) { for(int i = 0; i < path.fieldList.size(); ++i) { Object pathEl = path.fieldList.get(i); if(pathEl instanceof JDBCCMRFieldBridge) { addJoinPath(path); break; } } } buf = SQLUtil.getIsNullClause(node.not, field, alias, buf); } else if(child0 instanceof ASTParameter) { ASTParameter param = (ASTParameter) child0; Class type = getParameterType(param.number); QueryParameter queryParam = new QueryParameter(param.number - 1, typeFactory.getJDBCType(type)); inputParameters.add(queryParam); buf.append("? IS "); if(node.not) { buf.append(SQLUtil.NOT); } buf.append(SQLUtil.NULL); } else { throw new IllegalStateException("Unexpected node in IS NULL clause: " + node); } return buf; } public Object visit(ASTIsEmpty node, Object data) { StringBuffer buf = (StringBuffer) data; ASTPath path = (ASTPath) node.jjtGetChild(0); existsClause(path, buf, !node.not); return buf; } /** * Compare entity */ public Object visit(ASTMemberOf node, Object data) { StringBuffer buf = (StringBuffer) data; // setup compare to vars first, so we can compre types in from vars ASTPath toPath = (ASTPath) node.jjtGetChild(1); JDBCCMRFieldBridge toCMRField = (JDBCCMRFieldBridge) toPath.getCMRField(); JDBCEntityBridge toChildEntity = (JDBCEntityBridge) toPath.getEntity(); String pathStr = toPath.getPath(toPath.size() - 2); String toParentAlias = aliasManager.getAlias(pathStr); String toChildAlias = aliasManager.getAlias(toPath.getPath()); String relationTableAlias = null; if(toCMRField.getRelationMetaData().isTableMappingStyle()) { relationTableAlias = aliasManager.getRelationTableAlias(toPath.getPath()); } // setup from variables String fromAlias = null; int fromParamNumber = -1; if(node.jjtGetChild(0) instanceof ASTParameter) { ASTParameter fromParam = (ASTParameter) node.jjtGetChild(0); // can only compare like kind entities verifyParameterEntityType(fromParam.number, toChildEntity); fromParamNumber = fromParam.number; } else { ASTPath fromPath = (ASTPath) node.jjtGetChild(0); addJoinPath(fromPath); JDBCEntityBridge fromEntity = (JDBCEntityBridge) fromPath.getEntity(); fromAlias = aliasManager.getAlias(fromPath.getPath()); // can only compare like kind entities if(!fromEntity.equals(toChildEntity)) { throw new IllegalStateException("Only like types can be " + "compared: from entity=" + fromEntity.getEntityName() + " to entity=" + toChildEntity.getEntityName()); } } // add the path to the list of paths to left join addLeftJoinPath(pathStr, toPath); // first part makes toChild not in toParent.child if(!subquerySupported) { addJoinPath(toPath); // subquery not supported; use a left join and is not null if(node.not) { buf.append(SQLUtil.NOT); } buf.append('('); if(relationTableAlias == null) { SQLUtil.getIsNullClause(true, toChildEntity.getPrimaryKeyFields(), toChildAlias, buf); } else { SQLUtil.getIsNullClause(true, toCMRField.getTableKeyFields(), relationTableAlias, buf); } } else { // subquery supported; use exists subquery if(node.not) { buf.append(SQLUtil.NOT); } buf.append(SQLUtil.EXISTS).append('('); if(relationTableAlias == null) { buf.append(SQLUtil.SELECT); SQLUtil.getColumnNamesClause(toChildEntity.getPrimaryKeyFields(), toChildAlias, buf) .append(SQLUtil.FROM) .append(toChildEntity.getQualifiedTableName()) .append(' ') .append(toChildAlias) .append(SQLUtil.WHERE); SQLUtil.getJoinClause(toCMRField, toParentAlias, toChildAlias, buf); } else { buf.append(SQLUtil.SELECT); SQLUtil.getColumnNamesClause(toCMRField.getRelatedCMRField().getTableKeyFields(), relationTableAlias, buf) .append(SQLUtil.FROM) .append(toCMRField.getQualifiedTableName()) .append(' ') .append(relationTableAlias) .append(SQLUtil.WHERE); SQLUtil.getRelationTableJoinClause(toCMRField, toParentAlias, relationTableAlias, buf); } } buf.append(SQLUtil.AND); // second part makes fromNode equal toChild if(fromAlias != null) { // compre pk to pk if(relationTableAlias == null) { SQLUtil.getSelfCompareWhereClause(toChildEntity.getPrimaryKeyFields(), toChildAlias, fromAlias, buf); } else { SQLUtil.getRelationTableJoinClause(toCMRField.getRelatedCMRField(), fromAlias, relationTableAlias, buf); } } else { // add the parameters inputParameters.addAll(QueryParameter.createParameters(fromParamNumber - 1, toChildEntity)); // compare pk to parameter if(relationTableAlias == null) { SQLUtil.getWhereClause(toChildEntity.getPrimaryKeyFields(), toChildAlias, buf); } else { SQLUtil.getWhereClause(toCMRField.getRelatedCMRField().getTableKeyFields(), relationTableAlias, buf); } } buf.append(')'); return buf; } public Object visit(ASTValueClassComparison node, Object data) { StringBuffer buf = (StringBuffer) data; boolean not = (node.opp.equals(SQLUtil.NOT_EQUAL)); String comparison = node.opp; buf.append('('); if(not) { buf.append(SQLUtil.NOT).append('('); comparison = "="; } // setup the from path ASTPath fromPath = (ASTPath) node.jjtGetChild(0); addJoinPath(fromPath); String fromAlias = aliasManager.getAlias(fromPath.getPath(fromPath.size() - 2)); JDBCCMPFieldBridge fromCMPField = (JDBCCMPFieldBridge) fromPath.getCMPField(); Node toNode = node.jjtGetChild(1); if(toNode instanceof ASTParameter) { ASTParameter toParam = (ASTParameter) toNode; // can only compare like kind entities Class parameterType = getParameterType(toParam.number); if(!(fromCMPField.getFieldType().equals(parameterType))) { throw new IllegalStateException("Only like types can be " + "compared: from CMP field=" + fromCMPField.getFieldType() + " to parameter=" + parameterType); } inputParameters.addAll(QueryParameter.createParameters(toParam.number - 1, fromCMPField)); SQLUtil.getWhereClause(fromCMPField.getJDBCType(), fromAlias, comparison, buf); } else { ASTPath toPath = (ASTPath) toNode; addJoinPath(toPath); String toAlias = aliasManager.getAlias(toPath.getPath(toPath.size() - 2)); JDBCCMPFieldBridge toCMPField = (JDBCCMPFieldBridge) toPath.getCMPField(); // can only compare like kind entities if(!(fromCMPField.getFieldType().equals(toCMPField.getFieldType()))) { throw new IllegalStateException("Only like types can be " + "compared: from CMP field=" + fromCMPField.getFieldType() + " to CMP field=" + toCMPField.getFieldType()); } SQLUtil.getSelfCompareWhereClause(fromCMPField, toCMPField, fromAlias, toAlias, comparison, buf); } return (not ? buf.append(')') : buf).append(')'); } /** * compreEntity(arg0, arg1) */ public Object visit(ASTEntityComparison node, Object data) { StringBuffer buf = (StringBuffer) data; Node arg0 = node.jjtGetChild(0); Node arg1 = node.jjtGetChild(1); if(node.opp.equals(SQLUtil.NOT_EQUAL)) { compareEntity(true, arg0, arg1, buf); } else { compareEntity(false, arg0, arg1, buf); } return buf; } /** * Type-mapping function translation */ public Object visit(ASTConcat node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.CONCAT); Object[] args = new Object[]{ new NodeStringWrapper(node.jjtGetChild(0)), new NodeStringWrapper(node.jjtGetChild(1)), }; function.getFunctionSql(args, buf); return buf; } /** * Type-mapping function translation */ public Object visit(ASTSubstring node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.SUBSTRING); Object[] args = new Object[]{ new NodeStringWrapper(node.jjtGetChild(0)), new NodeStringWrapper(node.jjtGetChild(1)), new NodeStringWrapper(node.jjtGetChild(2)), }; function.getFunctionSql(args, buf); return buf; } /** * Type-mapping function translation */ public Object visit(ASTLCase node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.LCASE); Object[] args = new Object[]{ new NodeStringWrapper(node.jjtGetChild(0)), }; function.getFunctionSql(args, buf); return buf; } /** * Type-mapping function translation */ public Object visit(ASTUCase node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.UCASE); Object[] args = new Object[]{ new NodeStringWrapper(node.jjtGetChild(0)), }; function.getFunctionSql(args, buf); return buf; } /** * Type-mapping function translation */ public Object visit(ASTLength node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.LENGTH); Object[] args = new Object[]{ new NodeStringWrapper(node.jjtGetChild(0)), }; function.getFunctionSql(args, buf); return buf; } /** * Type-mapping function translation */ public Object visit(ASTLocate node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.LOCATE); Object[] args = new Object[3]; args[0] = new NodeStringWrapper(node.jjtGetChild(0)); args[1] = new NodeStringWrapper(node.jjtGetChild(1)); if(node.jjtGetNumChildren() == 3) { args[2] = new NodeStringWrapper(node.jjtGetChild(2)); } else { args[2] = "1"; } // add the sql to the current buffer function.getFunctionSql(args, buf); return buf; } /** * Type-mapping function translation */ public Object visit(ASTAbs node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.ABS); Object[] args = new Object[]{ new NodeStringWrapper(node.jjtGetChild(0)), }; function.getFunctionSql(args, buf); return buf; } /** * Type-mapping function translation */ public Object visit(ASTMod node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.MOD); Object[] args = new Object[]{ new NodeStringWrapper(node.jjtGetChild(0)), new NodeStringWrapper(node.jjtGetChild(1)), }; function.getFunctionSql(args, buf); return buf; } /** * Type-mapping function translation */ public Object visit(ASTSqrt node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.SQRT); Object[] args = new Object[]{ new NodeStringWrapper(node.jjtGetChild(0)) }; function.getFunctionSql(args, buf); return buf; } public Object visit(ASTCount node, Object data) { StringBuffer buf = (StringBuffer) data; node.setResultType(returnType); Object args[]; final ASTPath cntPath = (ASTPath) node.jjtGetChild(0); if(cntPath.isCMPField()) { args = new Object[]{node.distinct, new NodeStringWrapper(cntPath)}; } else { JDBCEntityBridge entity = (JDBCEntityBridge) cntPath.getEntity(); final JDBCFieldBridge[] pkFields = entity.getPrimaryKeyFields(); if(pkFields.length > 1) { countCompositePk = true; forceDistinct = node.distinct.length() > 0; selectEntity(cntPath, forceDistinct, buf); return buf; } else { final String alias = aliasManager.getAlias(cntPath.getPath()); StringBuffer keyColumn = new StringBuffer(20); SQLUtil.getColumnNamesClause(pkFields[0], alias, keyColumn); args = new Object[]{node.distinct, keyColumn.toString()}; } } return JDBCTypeMappingMetaData.COUNT_FUNC.getFunctionSql(args, buf); } public Object visit(ASTMax node, Object data) { if(functionJDBCType != null) { node.setResultType(functionJDBCType.getJavaTypes()[0]); node.setJDBCType(functionJDBCType); } else node.setResultType(returnType); StringBuffer buf = (StringBuffer) data; Object[] args = new Object[]{ node.distinct, new NodeStringWrapper(node.jjtGetChild(0)) }; return JDBCTypeMappingMetaData.MAX_FUNC.getFunctionSql(args, buf); } public Object visit(ASTMin node, Object data) { if(functionJDBCType != null) { node.setResultType(functionJDBCType.getJavaTypes()[0]); node.setJDBCType(functionJDBCType); } else node.setResultType(returnType); StringBuffer buf = (StringBuffer) data; Object[] args = new Object[]{ node.distinct, new NodeStringWrapper(node.jjtGetChild(0)) }; return JDBCTypeMappingMetaData.MIN_FUNC.getFunctionSql(args, buf); } public Object visit(ASTAvg node, Object data) { if(functionJDBCType != null) { node.setResultType(functionJDBCType.getJavaTypes()[0]); node.setJDBCType(functionJDBCType); } else node.setResultType(returnType); StringBuffer buf = (StringBuffer) data; Object[] args = new Object[]{ node.distinct, new NodeStringWrapper(node.jjtGetChild(0)), }; return JDBCTypeMappingMetaData.AVG_FUNC.getFunctionSql(args, buf); } public Object visit(ASTSum node, Object data) { if(functionJDBCType != null) { node.setResultType(functionJDBCType.getJavaTypes()[0]); node.setJDBCType(functionJDBCType); } else node.setResultType(returnType); StringBuffer buf = (StringBuffer) data; Object[] args = new Object[]{ node.distinct, new NodeStringWrapper(node.jjtGetChild(0)) }; return JDBCTypeMappingMetaData.SUM_FUNC.getFunctionSql(args, buf); } /** * tableAlias.columnName */ public Object visit(ASTPath node, Object data) { StringBuffer buf = (StringBuffer) data; if(!node.isCMPField()) { throw new IllegalStateException("Can only visit cmp valued path " + "node. Should have been handled at a higher level."); } JDBCCMPFieldBridge cmpField = (JDBCCMPFieldBridge) node.getCMPField(); // make sure this is mapped to a single column switch(node.type) { case EJBQLTypes.ENTITY_TYPE: case EJBQLTypes.VALUE_CLASS_TYPE: if(cmpField.getJDBCType().hasMapper() || cmpField.getJDBCType().getParameterSetter() != null) { break; } case EJBQLTypes.UNKNOWN_TYPE: throw new IllegalStateException("Can not visit multi-column path " + "node. Should have been handled at a higher level."); } addJoinPath(node); String alias = aliasManager.getAlias(node.getPath(node.size() - 2)); SQLUtil.getColumnNamesClause(cmpField, alias, buf); return buf; } public Object visit(ASTAbstractSchema node, Object data) { throw new IllegalStateException("Can not visit abstract schema node. " + "Should have been handled at a higher level."); } /** * ? */ public Object visit(ASTParameter node, Object data) { StringBuffer buf = (StringBuffer) data; Class type = getParameterType(node.number); // make sure this is mapped to a single column int ejbqlType = EJBQLTypes.getEJBQLType(type); if(ejbqlType == EJBQLTypes.ENTITY_TYPE || ejbqlType == EJBQLTypes.VALUE_CLASS_TYPE || ejbqlType == EJBQLTypes.UNKNOWN_TYPE) { throw new IllegalStateException("Can not visit multi-column " + "parameter node. Should have been handled at a higher level."); } QueryParameter param = new QueryParameter(node.number - 1, typeFactory.getJDBCType(type)); inputParameters.add(param); buf.append('?'); return buf; } /** * typeMapping.get<True/False>Mapping() */ public Object visit(ASTBooleanLiteral node, Object data) { StringBuffer buf = (StringBuffer) data; if(node.value) { buf.append(typeMapping.getTrueMapping()); } else { buf.append(typeMapping.getFalseMapping()); } return data; } public Object visit(ASTLimitOffset node, Object data) { int child = 0; if(node.hasOffset) { Node offsetNode = node.jjtGetChild(child++); if(offsetNode instanceof ASTParameter) { ASTParameter param = (ASTParameter) offsetNode; Class parameterType = getParameterType(param.number); if(int.class != parameterType && Integer.class != parameterType) { throw new UnsupportedOperationException("OFFSET parameter must be an int"); } offsetParam = param.number; } else { ASTExactNumericLiteral param = (ASTExactNumericLiteral) offsetNode; offsetValue = (int) param.value; } } if(node.hasLimit) { Node limitNode = node.jjtGetChild(child); if(limitNode instanceof ASTParameter) { ASTParameter param = (ASTParameter) limitNode; Class parameterType = getParameterType(param.number); if(int.class != parameterType && Integer.class != parameterType) { throw new UnsupportedOperationException("LIMIT parameter must be an int"); } limitParam = param.number; } else { ASTExactNumericLiteral param = (ASTExactNumericLiteral) limitNode; limitValue = (int) param.value; } } return data; } public Object visit(ASTWhereConditionalTerm node, Object data) { // clear per term paths clearPerTermJoinPaths(); StringBuffer buf = (StringBuffer) data; buf.append('('); for(int i = 0; i < node.jjtGetNumChildren(); ++i) { node.jjtGetChild(i).jjtAccept(this, data); } StringBuffer thetaJoin = new StringBuffer(); createThetaJoin(thetaJoin); if(thetaJoin.length() > 0) { buf.append(SQLUtil.AND).append(thetaJoin.toString()); } buf.append(')'); return data; } /** * Wrap a node with a class that when ever toString is called visits the * node. This is used by the function implmentations, for parameters. * <p/> * Be careful with this class because it visits the node for each call of * toString, which could have undesireable result if called multiple times. */ private final class NodeStringWrapper { final Node node; public NodeStringWrapper(Node node) { this.node = node; } public String toString() { return node.jjtAccept(JDBCEJBQLCompiler.this, new StringBuffer()).toString(); } } /** * Recursively searches for ASTPath among children. * * @param selectFunction a node implements SelectFunction * @return ASTPath child or null if there was no child of type ASTPath */ private ASTPath getPathFromChildren(Node selectFunction) { for(int childInd = 0; childInd < selectFunction.jjtGetNumChildren(); ++childInd) { Node child = selectFunction.jjtGetChild(childInd); if(child instanceof ASTPath) { return (ASTPath) child; } else if(child instanceof SelectFunction) { Node path = getPathFromChildren(child); if(path != null) { return (ASTPath) path; } } } return null; } /** * Checks whether the path passed in is already in the SELECT clause. * * @param path the path to check. * @return true if the path is already in the SELECT clause. */ private boolean isSelected(ASTPath path) { boolean selected = false; CMPFieldBridge cmpField = path.getCMPField(); if(selectObject instanceof JDBCCMPFieldBridge && cmpField == selectObject) { selected = true; } else if(selectObject instanceof JDBCEntityBridge) { JDBCEntityBridge entity = (JDBCEntityBridge) selectObject; JDBCFieldBridge[] pkFields = entity.getPrimaryKeyFields(); for(int pkInd = 0; pkInd < pkFields.length; ++pkInd) { if(pkFields[pkInd] == cmpField) { selected = true; break; } } } else if(selectObject instanceof SelectFunction) { Node funcNode = (Node) selectObject; ASTPath fieldPath = getPathFromChildren(funcNode); if(fieldPath.getCMPField() == cmpField) { selected = true; } } return selected; } private void selectEntity(ASTPath path, boolean distinct, StringBuffer buf) { JDBCEntityBridge selectEntity = (JDBCEntityBridge) path.getEntity(); StringBuffer columnNamesClause = new StringBuffer(200); addJoinPath(path); selectAlias = aliasManager.getAlias(path.getPath()); // get a list of all fields to be loaded // get the identifier for this field SQLUtil.getColumnNamesClause(selectEntity.getPrimaryKeyFields(), selectAlias, columnNamesClause); if(readAhead.isOnFind()) { String eagerLoadGroupName = readAhead.getEagerLoadGroup(); boolean[] loadGroupMask = selectEntity.getLoadGroupMask(eagerLoadGroupName); if(distinct) SQLUtil.appendSearchableColumnNamesClause(selectEntity.getTableFields(), loadGroupMask, selectAlias, columnNamesClause); else SQLUtil.appendColumnNamesClause(selectEntity.getTableFields(), loadGroupMask, selectAlias, columnNamesClause); try { leftJoinCMRList = JDBCAbstractQueryCommand.getLeftJoinCMRNodes( selectEntity, path.getPath(), readAhead.getLeftJoins(), declaredPaths); } catch(DeploymentException e) { throw new IllegalStateException(e.getMessage()); } if(!leftJoinCMRList.isEmpty()) { onFindCMRJoin = new StringBuffer(100); JDBCAbstractQueryCommand.leftJoinCMRNodes(selectAlias, leftJoinCMRList, aliasManager, onFindCMRJoin); JDBCAbstractQueryCommand.appendLeftJoinCMRColumnNames(leftJoinCMRList, aliasManager, columnNamesClause); } } buf.append(columnNamesClause); } private void addJoinPath(ASTPath path) { ctermJoinPaths.add(path); allJoinPaths.add(path); } private void addCollectionMemberJoinPath(String alias, ASTPath path) { ctermCollectionMemberJoinPaths.put(alias, path); allCollectionMemberJoinPaths.put(alias, path); } private void addLeftJoinPath(String pathStr, ASTPath path) { Set set = (Set) ctermLeftJoinPaths.get(pathStr); if(set == null) { set = new HashSet(); ctermLeftJoinPaths.put(pathStr, set); } set.add(path); set = (Set) allLeftJoinPaths.get(pathStr); if(set == null) { set = new HashSet(); allLeftJoinPaths.put(pathStr, set); } set.add(path); } private void clearPerTermJoinPaths() { ctermJoinPaths.clear(); ctermCollectionMemberJoinPaths.clear(); ctermLeftJoinPaths.clear(); } private boolean isDistinct(Node selectNode) { return ((ASTSelect) selectNode).distinct || returnType.equals(Set.class) || forceDistinct; } }