/* * 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.List; import java.util.Set; import java.util.HashSet; import java.util.Map; import java.util.HashMap; import java.util.Iterator; import org.jboss.ejb.plugins.cmp.ejbql.*; 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.bridge.JDBCAbstractCMRFieldBridge; import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMPFieldBridge; 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.JDBCRelationMetaData; import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCFunctionMappingMetaData; 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.logging.Logger; /** * Compiles EJB-QL and JBossQL into SQL using OUTER and INNER joins. * * @author <a href="mailto:alex@jboss.org">Alex Loubyansky</a> * @version $Revision: 106022 $ */ public final class EJBQLToSQL92Compiler implements QLCompiler, JBossQLParserVisitor { private static final Logger log = Logger.getLogger(EJBQLToSQL92Compiler.class); // input objects private final Catalog catalog; private Class returnType; private Class[] parameterTypes; private JDBCReadAheadMetaData readAhead; // alias info private AliasManager aliasManager; private Map joinPaths = new HashMap(); private Map identifierToTable = new HashMap(); private Set joinedAliases = new HashSet(); // mapping metadata private JDBCTypeMappingMetaData typeMapping; private JDBCTypeFactory typeFactory; // output objects private boolean forceDistinct; private String sql; private int offsetParam; private int offsetValue; private int limitParam; private int limitValue; private JDBCEntityPersistenceStore selectManager; private Object selectObject; private List inputParameters = new ArrayList(); private JDBCType functionJDBCType; private List leftJoinCMRList = new ArrayList(); private StringBuffer onFindCMRJoin; private boolean countCompositePk; private boolean selectDistinct; public EJBQLToSQL92Compiler(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(); // get the parser EJBQLParser parser = new EJBQLParser(new StringReader("")); try { // parse the ejbql into an abstract sytax tree ASTEJBQL 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(); // get the parser JBossQLParser parser = new JBossQLParser(new StringReader("")); try { // parse the ejbql into an abstract sytax tree ASTEJBQL ejbqlNode = parser.parse(catalog, parameterTypes, ejbql); // translate to sql sql = ejbqlNode.jjtAccept(this, new StringBuffer()).toString(); if(log.isTraceEnabled()) { log.trace("ejbql: " + ejbql); log.trace("sql: " + sql); } } 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 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 JDBCAbstractEntityBridge; } public JDBCAbstractEntityBridge getSelectEntity() { return (JDBCAbstractEntityBridge) selectObject; } public boolean isSelectField() { boolean result; if(selectObject instanceof JDBCFieldBridge) { JDBCFieldBridge field = (JDBCFieldBridge) selectObject; result = field.isCMPField(); } else { result = false; } return result; } public JDBCFieldBridge getSelectField() { return (JDBCFieldBridge) 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); } public Object visit(ASTEJBQL node, Object data) { Node selectNode = node.jjtGetChild(0); Node fromNode = node.jjtGetChild(1); // compile selectNode StringBuffer selectClause = new StringBuffer(50); selectNode.jjtAccept(this, selectClause); StringBuffer whereClause = null; StringBuffer orderByClause = null; for(int i = 2; i < node.jjtGetNumChildren(); ++i) { Node childNode = node.jjtGetChild(i); if(childNode instanceof ASTWhere) { whereClause = new StringBuffer(20); childNode.jjtAccept(this, whereClause); } else if(childNode instanceof ASTOrderBy) { orderByClause = new StringBuffer(); childNode.jjtAccept(this, orderByClause); } else if(childNode instanceof ASTLimitOffset) { childNode.jjtAccept(this, null); } } // compile fromNode StringBuffer fromClause = new StringBuffer(30); fromNode.jjtAccept(this, fromClause); // left-join for(Iterator iter = identifierToTable.entrySet().iterator(); iter.hasNext();) { final Map.Entry entry = (Map.Entry) iter.next(); final String identifier = (String) entry.getKey(); final String table = (String) entry.getValue(); final String alias = aliasManager.getAlias(identifier); fromClause.append(table).append(' ').append(alias); join(alias, fromClause); if(iter.hasNext()) { fromClause.append(SQLUtil.COMMA); } } selectDistinct = ((ASTSelect) selectNode).distinct || returnType == Set.class || forceDistinct; // assemble sql StringBuffer sql = (StringBuffer) data; if(selectManager.getMetaData().hasRowLocking() && !(selectObject instanceof SelectFunction)) { JDBCFunctionMappingMetaData rowLockingTemplate = typeMapping.getRowLockingTemplate(); if(rowLockingTemplate == null) { throw new IllegalStateException("Row locking template is not defined for given mapping: " + typeMapping.getName()); } boolean distinct = selectDistinct; Object args[] = new Object[]{ distinct ? SQLUtil.DISTINCT + selectClause : selectClause.toString(), fromClause, whereClause == null || whereClause.length() == 0 ? null : whereClause, orderByClause == null || orderByClause.length() == 0 ? null : orderByClause }; rowLockingTemplate.getFunctionSql(args, sql); } else { sql.append(SQLUtil.SELECT); if(selectDistinct) { sql.append(SQLUtil.DISTINCT); } sql.append(selectClause) .append(SQLUtil.FROM) .append(fromClause); if(whereClause != null && whereClause.length() > 0) { sql.append(SQLUtil.WHERE).append(whereClause); } if(orderByClause != null && orderByClause.length() > 0) { sql.append(SQLUtil.ORDERBY).append(orderByClause); } } if(countCompositePk) { sql.insert(0, "SELECT COUNT(*) FROM (").append(") t_count"); } return data; } public Object visit(ASTOrderBy node, Object data) { StringBuffer buf = (StringBuffer) data; node.jjtGetChild(0).jjtAccept(this, data); for(int i = 1; i < node.jjtGetNumChildren(); i++) { buf.append(SQLUtil.COMMA); node.jjtGetChild(i).jjtAccept(this, data); } return data; } public Object visit(ASTOrderByPath node, Object data) { StringBuffer buf = (StringBuffer) data; node.jjtGetChild(0).jjtAccept(this, data); if(node.ascending) { buf.append(SQLUtil.ASC); } else { buf.append(SQLUtil.DESC); } 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 IllegalStateException("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 IllegalStateException("LIMIT parameter must be an int"); } limitParam = param.number; } else { ASTExactNumericLiteral param = (ASTExactNumericLiteral) limitNode; limitValue = (int) param.value; } } return data; } public Object visit(ASTSelect select, Object data) { StringBuffer sql = (StringBuffer) data; final Node child0 = select.jjtGetChild(0); final ASTPath path; if(child0 instanceof ASTPath) { path = (ASTPath) child0; if(path.isCMPField()) { // set the select object JDBCFieldBridge selectField = (JDBCFieldBridge) path.getCMPField(); selectManager = selectField.getManager(); selectObject = selectField; setTypeFactory(selectManager.getJDBCTypeFactory()); // todo inner or left? //addLeftJoinPath(path); addInnerJoinPath(path); String alias = aliasManager.getAlias(path.getPath(path.size() - 2)); SQLUtil.getColumnNamesClause(selectField, alias, sql); } else { JDBCAbstractEntityBridge selectEntity = (JDBCAbstractEntityBridge) path.getEntity(); selectManager = selectEntity.getManager(); selectObject = selectEntity; setTypeFactory(selectEntity.getManager().getJDBCTypeFactory()); final String alias = aliasManager.getAlias(path.getPath()); if(select.distinct) { SQLUtil.getSearchableColumnNamesClause(selectEntity.getTableFields(), alias, sql); } else { SQLUtil.getColumnNamesClause(selectEntity.getTableFields(), alias, sql); } /* if(readAhead.isOnFind()) { String eagerLoadGroupName = readAhead.getEagerLoadGroup(); boolean[] loadGroupMask = selectEntity.getLoadGroupMask(eagerLoadGroupName); SQLUtil.appendColumnNamesClause( selectEntity.getTableFields(), loadGroupMask, alias, sql ); } */ addLeftJoinPath(path); } } 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()) { JDBCFieldBridge selectField = (JDBCFieldBridge) path.getCMPField(); selectManager = selectField.getManager(); setTypeFactory(selectManager.getJDBCTypeFactory()); if(selectField.getJDBCType().hasMapper()) this.functionJDBCType = selectField.getJDBCType(); } else if(path.isCMRField()) { JDBCFieldBridge cmrField = (JDBCFieldBridge) path.getCMRField(); selectManager = cmrField.getManager(); setTypeFactory(selectManager.getJDBCTypeFactory()); addLeftJoinPath(path); } else { final JDBCAbstractEntityBridge entity = (JDBCAbstractEntityBridge) path.getEntity(); selectManager = entity.getManager(); setTypeFactory(selectManager.getJDBCTypeFactory()); addLeftJoinPath(path); } selectObject = child0; child0.jjtAccept(this, data); } return data; } public Object visit(ASTWhere node, Object data) { node.jjtGetChild(0).jjtAccept(this, data); return data; } public Object visit(ASTOr node, Object data) { StringBuffer buf = (StringBuffer) data; node.jjtGetChild(0).jjtAccept(this, data); for(int i = 1; i < node.jjtGetNumChildren(); ++i) { buf.append(SQLUtil.OR); node.jjtGetChild(i).jjtAccept(this, data); } return data; } public Object visit(ASTWhereConditionalTerm node, Object data) { for(int i = 0; i < node.jjtGetNumChildren(); ++i) { node.jjtGetChild(i).jjtAccept(this, data); } return data; } public Object visit(ASTAnd node, Object data) { StringBuffer buf = (StringBuffer) data; node.jjtGetChild(0).jjtAccept(this, data); for(int i = 1; i < node.jjtGetNumChildren(); i++) { buf.append(SQLUtil.AND); node.jjtGetChild(i).jjtAccept(this, data); } return data; } public Object visit(ASTNot node, Object data) { StringBuffer buf = (StringBuffer) data; buf.append(SQLUtil.NOT); node.jjtGetChild(0).jjtAccept(this, data); return data; } public Object visit(ASTConditionalParenthetical node, Object data) { StringBuffer buf = (StringBuffer) data; buf.append('('); node.jjtGetChild(0).jjtAccept(this, data); buf.append(')'); return data; } public Object visit(ASTBetween node, Object data) { StringBuffer buf = (StringBuffer) data; node.jjtGetChild(0).jjtAccept(this, data); if(node.not) { buf.append(SQLUtil.NOT); } buf.append(SQLUtil.BETWEEN); node.jjtGetChild(1).jjtAccept(this, data); buf.append(SQLUtil.AND); node.jjtGetChild(2).jjtAccept(this, data); return data; } public Object visit(ASTIn node, Object data) { StringBuffer buf = (StringBuffer) data; node.jjtGetChild(0).jjtAccept(this, data); if(node.not) { buf.append(SQLUtil.NOT); } buf.append(SQLUtil.IN).append('('); node.jjtGetChild(1).jjtAccept(this, data); for(int i = 2; i < node.jjtGetNumChildren(); i++) { buf.append(SQLUtil.COMMA); node.jjtGetChild(i).jjtAccept(this, data); } buf.append(')'); return data; } public Object visit(ASTLike node, Object data) { StringBuffer buf = (StringBuffer) data; node.jjtGetChild(0).jjtAccept(this, data); if(node.not) { buf.append(SQLUtil.NOT); } buf.append(SQLUtil.LIKE); node.jjtGetChild(1).jjtAccept(this, data); if(node.jjtGetNumChildren() == 3) { buf.append(" {ESCAPE "); node.jjtGetChild(2).jjtAccept(this, data); buf.append('}'); } return data; } public Object visit(ASTNullComparison node, Object data) { StringBuffer sql = (StringBuffer) data; final Node child0 = node.jjtGetChild(0); if(child0 instanceof ASTPath) { ASTPath path = (ASTPath) child0; addLeftJoinPath(path); JDBCFieldBridge field = (JDBCFieldBridge) path.getField(); if(field instanceof JDBCAbstractCMRFieldBridge) { JDBCAbstractCMRFieldBridge cmrField = (JDBCAbstractCMRFieldBridge)field; final String alias; final JDBCFieldBridge[] keyFields; if(cmrField.hasForeignKey()) { alias = aliasManager.getAlias(path.getPath(path.size() - 2)); keyFields = cmrField.getForeignKeyFields(); } else { alias = aliasManager.getAlias(path.getPath()); if(cmrField.getMetaData().getRelationMetaData().isTableMappingStyle()) { keyFields = cmrField.getRelatedCMRField().getEntity().getPrimaryKeyFields(); } else { keyFields = cmrField.getRelatedCMRField().getForeignKeyFields(); } } SQLUtil.getIsNullClause(node.not, keyFields, alias, sql); } else { String alias = aliasManager.getAlias(path.getPath(path.size() - 2)); SQLUtil.getIsNullClause(node.not, field, alias, sql); } } 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); sql.append("? IS "); if(node.not) { sql.append(SQLUtil.NOT); } sql.append(SQLUtil.NULL); } else { throw new IllegalStateException("Unexpected node in IS NULL clause: " + node); } return data; } public Object visit(ASTIsEmpty node, Object data) { ASTPath path = (ASTPath) node.jjtGetChild(0); if(!path.isCMRField()) { throw new IllegalStateException("IS EMPTY can be applied only to collection valued CMR field."); } addLeftJoinPath(path); StringBuffer sql = (StringBuffer) data; JDBCAbstractCMRFieldBridge cmrField = (JDBCAbstractCMRFieldBridge) path.getCMRField(); JDBCAbstractEntityBridge relatedEntity = (JDBCAbstractEntityBridge) cmrField.getRelatedEntity(); String alias = aliasManager.getAlias(path.getPath()); SQLUtil.getIsNullClause(node.not, relatedEntity.getPrimaryKeyFields(), alias, sql); return data; } public Object visit(ASTMemberOf node, Object data) { Node member = node.jjtGetChild(0); ASTPath colPath = (ASTPath) node.jjtGetChild(1); JDBCAbstractEntityBridge colEntity = (JDBCAbstractEntityBridge) colPath.getEntity(); StringBuffer sql = (StringBuffer) data; if(node.not) { sql.append(SQLUtil.NOT); } sql.append(SQLUtil.EXISTS).append('(').append(SQLUtil.SELECT); if(member instanceof ASTParameter) { ASTParameter toParam = (ASTParameter) member; verifyParameterEntityType(toParam.number, colEntity); inputParameters.addAll(QueryParameter.createParameters(toParam.number - 1, colEntity)); String parentAlias = aliasManager.getAlias(colPath.getPath(0)); String localParentAlias = aliasManager.getAlias(colPath.getPath(0) + "_local"); JDBCAbstractEntityBridge parentEntity = (JDBCAbstractEntityBridge) colPath.getEntity(0); SQLUtil.getColumnNamesClause(parentEntity.getPrimaryKeyFields(), localParentAlias, sql); sql.append(SQLUtil.FROM) .append(parentEntity.getQualifiedTableName()).append(' ').append(localParentAlias); innerJoinPath(colPath, sql); sql.append(SQLUtil.WHERE); JDBCAbstractEntityBridge col0 = (JDBCAbstractEntityBridge)colPath.getEntity(0); SQLUtil.getSelfCompareWhereClause(col0.getPrimaryKeyFields(), parentAlias, localParentAlias, sql); sql.append(SQLUtil.AND); String localColAlias = aliasManager.getAlias(colPath.getPath() + "_local"); SQLUtil.getWhereClause(colEntity.getPrimaryKeyFields(), localColAlias, sql); } else { ASTPath memberPath = (ASTPath) member; JDBCAbstractEntityBridge memberEntity = (JDBCAbstractEntityBridge) memberPath.getEntity(); if(!memberEntity.equals(colEntity)) { throw new IllegalStateException("Member must be if the same type as the collection, got: member=" + memberEntity.getEntityName() + ", collection=" + colEntity.getEntityName()); } String memberAlias = aliasManager.getAlias(memberPath.getPath()); if(memberPath.size() > 1) { String parentAlias = aliasManager.getAlias(memberPath.getPath(0) + "_local"); JDBCAbstractEntityBridge parentEntity = (JDBCAbstractEntityBridge) memberPath.getEntity(0); SQLUtil.getColumnNamesClause(parentEntity.getPrimaryKeyFields(), parentAlias, sql); sql.append(SQLUtil.FROM) .append(parentEntity.getQualifiedTableName()).append(' ').append(parentAlias); innerJoinPath(memberPath, sql); innerJoinPath(colPath, sql); } else if(colPath.size() > 1) { String parentAlias = aliasManager.getAlias(colPath.getPath(0) + "_local"); JDBCAbstractEntityBridge parentEntity = (JDBCAbstractEntityBridge) colPath.getEntity(0); SQLUtil.getColumnNamesClause(parentEntity.getPrimaryKeyFields(), parentAlias, sql); sql.append(SQLUtil.FROM) .append(parentEntity.getQualifiedTableName()).append(' ').append(parentAlias); innerJoinPath(colPath, sql); } else { throw new IllegalStateException( "There should be collection valued path expression, not identification variable."); } sql.append(SQLUtil.WHERE); JDBCAbstractEntityBridge member0 = (JDBCAbstractEntityBridge)memberPath.getEntity(0); String colAliasLocal = aliasManager.getAlias(colPath.getPath() + "_local"); if(memberPath.size() > 1) { String memberAliasLocal = aliasManager.getAlias(memberPath.getPath() + "_local"); SQLUtil.getSelfCompareWhereClause(colEntity.getPrimaryKeyFields(), memberAliasLocal, colAliasLocal, sql); sql.append(SQLUtil.AND); String member0Alias = aliasManager.getAlias(memberPath.getPath(0)); String member0AliasLocal = aliasManager.getAlias(memberPath.getPath(0) + "_local"); SQLUtil.getSelfCompareWhereClause(member0.getPrimaryKeyFields(), member0Alias, member0AliasLocal, sql); } else { SQLUtil.getSelfCompareWhereClause(member0.getPrimaryKeyFields(), memberAlias, colAliasLocal, sql); sql.append(SQLUtil.AND); String col0Alias = aliasManager.getAlias(colPath.getPath(0)); String col0AliasLocal = aliasManager.getAlias(colPath.getPath(0) + "_local"); SQLUtil.getSelfCompareWhereClause(colEntity.getPrimaryKeyFields(), col0Alias, col0AliasLocal, sql); } } sql.append(')'); return data; } private void innerJoinPath(ASTPath path, StringBuffer sql) { if(path.size() < 2) { return; } String parentAlias = aliasManager.getAlias(path.getPath(0) + "_local"); String leftAlias = parentAlias; for(int i = 1; i < path.size(); ++i) { String curPath = path.getPath(i); final String joinAlias = aliasManager.getAlias(curPath + "_local"); final JDBCAbstractCMRFieldBridge cmrField = (JDBCAbstractCMRFieldBridge) path.getCMRField(i); final JDBCAbstractEntityBridge joinEntity = (JDBCAbstractEntityBridge) cmrField.getRelatedEntity(); JDBCRelationMetaData relation = cmrField.getMetaData().getRelationMetaData(); String join = " INNER JOIN "; if(relation.isTableMappingStyle()) { String relTableAlias = aliasManager.getRelationTableAlias(curPath + "_local"); sql.append(join) .append(cmrField.getQualifiedTableName()) .append(' ') .append(relTableAlias) .append(" ON "); SQLUtil.getRelationTableJoinClause(cmrField, leftAlias, relTableAlias, sql); sql.append(join) .append(joinEntity.getQualifiedTableName()) .append(' ') .append(joinAlias) .append(" ON "); SQLUtil.getRelationTableJoinClause(cmrField.getRelatedCMRField(), joinAlias, relTableAlias, sql); } else { sql.append(join) .append(joinEntity.getQualifiedTableName()) .append(' ') .append(joinAlias) .append(" ON "); SQLUtil.getJoinClause(cmrField, leftAlias, joinAlias, sql); } leftAlias = joinAlias; } } public Object visit(ASTStringComparison node, Object data) { StringBuffer buf = (StringBuffer) data; node.jjtGetChild(0).jjtAccept(this, data); buf.append(' ').append(node.opp).append(' '); node.jjtGetChild(1).jjtAccept(this, data); return data; } public Object visit(ASTBooleanComparison node, Object data) { StringBuffer buf = (StringBuffer) data; node.jjtGetChild(0).jjtAccept(this, data); if(node.jjtGetNumChildren() == 2) { buf.append(' ').append(node.opp).append(' '); node.jjtGetChild(1).jjtAccept(this, data); } return data; } public Object visit(ASTDatetimeComparison node, Object data) { StringBuffer buf = (StringBuffer) data; node.jjtGetChild(0).jjtAccept(this, data); buf.append(' ').append(node.opp).append(' '); node.jjtGetChild(1).jjtAccept(this, data); return data; } 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); addInnerJoinPath(fromPath); String fromAlias = aliasManager.getAlias(fromPath.getPath(fromPath.size() - 2)); CMPFieldBridge fromCMPField = (CMPFieldBridge) 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; addInnerJoinPath(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(')'); } 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 data; } public Object visit(ASTArithmeticComparison node, Object data) { StringBuffer buf = (StringBuffer) data; node.jjtGetChild(0).jjtAccept(this, data); buf.append(' ').append(node.opp).append(' '); node.jjtGetChild(1).jjtAccept(this, data); return data; } public Object visit(ASTPlusMinus node, Object data) { StringBuffer buf = (StringBuffer) data; node.jjtGetChild(0).jjtAccept(this, data); for(int i = 1; i < node.jjtGetNumChildren(); i++) { buf.append(' ').append(node.opps.get(i - 1)).append(' '); node.jjtGetChild(i).jjtAccept(this, data); } return data; } public Object visit(ASTMultDiv node, Object data) { StringBuffer buf = (StringBuffer) data; node.jjtGetChild(0).jjtAccept(this, data); for(int i = 1; i < node.jjtGetNumChildren(); i++) { buf.append(' ').append(node.opps.get(i - 1)).append(' '); node.jjtGetChild(i).jjtAccept(this, data); } return data; } public Object visit(ASTNegation node, Object data) { StringBuffer buf = (StringBuffer) data; buf.append('-'); node.jjtGetChild(0).jjtAccept(this, data); return data; } public Object visit(ASTArithmeticParenthetical node, Object data) { StringBuffer buf = (StringBuffer) data; buf.append('('); node.jjtGetChild(0).jjtAccept(this, data); buf.append(')'); return data; } public Object visit(ASTStringParenthetical node, Object data) { StringBuffer buf = (StringBuffer) data; buf.append('('); node.jjtGetChild(0).jjtAccept(this, data); buf.append(')'); return data; } public Object visit(ASTConcat node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.CONCAT); Object[] args = childrenToStringArr(2, node); function.getFunctionSql(args, buf); return data; } public Object visit(ASTSubstring node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.SUBSTRING); Object[] args = childrenToStringArr(3, node); function.getFunctionSql(args, buf); return data; } public Object visit(ASTUCase node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.UCASE); Object[] args = childrenToStringArr(1, node); function.getFunctionSql(args, buf); return data; } public Object visit(ASTLCase node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.LCASE); Object[] args = childrenToStringArr(1, node); function.getFunctionSql(args, buf); return data; } public Object visit(ASTLength node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.LENGTH); Object[] args = childrenToStringArr(1, node); function.getFunctionSql(args, buf); return data; } public Object visit(ASTLocate node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.LOCATE); Object[] args = new Object[3]; args[0] = node.jjtGetChild(0).jjtAccept(this, new StringBuffer()).toString(); args[1] = node.jjtGetChild(1).jjtAccept(this, new StringBuffer()).toString(); if(node.jjtGetNumChildren() == 3) { args[2] = node.jjtGetChild(2).jjtAccept(this, new StringBuffer()).toString(); } else { args[2] = "1"; } function.getFunctionSql(args, buf); return data; } public Object visit(ASTAbs node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.ABS); Object[] args = childrenToStringArr(1, node); function.getFunctionSql(args, buf); return data; } public Object visit(ASTSqrt node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.SQRT); Object[] args = childrenToStringArr(1, node); function.getFunctionSql(args, buf); return data; } public Object visit(ASTMod node, Object data) { StringBuffer buf = (StringBuffer) data; JDBCFunctionMappingMetaData function = typeMapping.getFunctionMapping(JDBCTypeMappingMetaData.MOD); Object[] args = childrenToStringArr(2, node); function.getFunctionSql(args, buf); return data; } 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, node.jjtGetChild(0).jjtAccept(this, new StringBuffer()).toString(), }; JDBCTypeMappingMetaData.AVG_FUNC.getFunctionSql(args, buf); return data; } 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, node.jjtGetChild(0).jjtAccept(this, new StringBuffer()).toString(), }; JDBCTypeMappingMetaData.MAX_FUNC.getFunctionSql(args, buf); return data; } 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, node.jjtGetChild(0).jjtAccept(this, new StringBuffer()).toString(), }; JDBCTypeMappingMetaData.MIN_FUNC.getFunctionSql(args, buf); return data; } 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, node.jjtGetChild(0).jjtAccept(this, new StringBuffer()).toString(), }; JDBCTypeMappingMetaData.SUM_FUNC.getFunctionSql(args, buf); return data; } 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, node.jjtGetChild(0).jjtAccept(this, new StringBuffer()).toString()}; } else { JDBCAbstractEntityBridge entity = (JDBCAbstractEntityBridge) cntPath.getEntity(); final JDBCFieldBridge[] pkFields = entity.getPrimaryKeyFields(); if(pkFields.length > 1) { countCompositePk = true; forceDistinct = node.distinct.length() > 0; addLeftJoinPath(cntPath); String alias = aliasManager.getAlias(cntPath.getPath()); SQLUtil.getColumnNamesClause(entity.getPrimaryKeyFields(), alias, 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()}; } } JDBCTypeMappingMetaData.COUNT_FUNC.getFunctionSql(args, buf); return data; } 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."); } JDBCFieldBridge cmpField = (JDBCFieldBridge) 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."); } addLeftJoinPath(node); String alias = aliasManager.getAlias(node.getPath(node.size() - 2)); SQLUtil.getColumnNamesClause(cmpField, alias, buf); return data; } 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(ASTIdentifier node, Object data) { throw new UnsupportedOperationException("Must not visit ASTIdentifier noe."); } 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 data; } public Object visit(ASTExactNumericLiteral node, Object data) { StringBuffer buf = (StringBuffer) data; buf.append(node.literal); return data; } public Object visit(ASTApproximateNumericLiteral node, Object data) { StringBuffer buf = (StringBuffer) data; buf.append(node.literal); return data; } public Object visit(ASTStringLiteral node, Object data) { StringBuffer buf = (StringBuffer) data; buf.append(node.value); return data; } 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(ASTFrom from, Object data) { StringBuffer sql = (StringBuffer) data; from.jjtGetChild(0).jjtAccept(this, data); for(int i = 1; i < from.jjtGetNumChildren(); ++i) { from.jjtGetChild(i).jjtAccept(this, data); } return data; } public Object visit(ASTCollectionMemberDeclaration node, Object data) { ASTPath path = (ASTPath) node.jjtGetChild(0); // assign the same alias for path and identifier ASTIdentifier id = (ASTIdentifier) node.jjtGetChild(1); String alias = aliasManager.getAlias(id.identifier); aliasManager.addAlias(path.getPath(), alias); addInnerJoinPath(path); return data; } public Object visit(ASTRangeVariableDeclaration node, Object data) { ASTAbstractSchema schema = (ASTAbstractSchema) node.jjtGetChild(0); JDBCAbstractEntityBridge entity = (JDBCAbstractEntityBridge) schema.entity; ASTIdentifier id = (ASTIdentifier) node.jjtGetChild(1); declareTable(id.identifier, entity.getQualifiedTableName()); return data; } // Private private void compareEntity(boolean not, Node fromNode, Node toNode, StringBuffer buf) { buf.append('('); if(not) { buf.append(SQLUtil.NOT).append('('); } ASTPath fromPath = (ASTPath) fromNode; addLeftJoinPath(fromPath); String fromAlias = aliasManager.getAlias(fromPath.getPath()); JDBCAbstractEntityBridge fromEntity = (JDBCAbstractEntityBridge) 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 { ASTPath toPath = (ASTPath) toNode; addLeftJoinPath(toPath); String toAlias = aliasManager.getAlias(toPath.getPath()); JDBCAbstractEntityBridge toEntity = (JDBCAbstractEntityBridge) 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 join(String alias, StringBuffer sql) { Map paths = (Map) joinPaths.get(alias); if(paths == null || paths.isEmpty()) { return; } for(Iterator iter = paths.values().iterator(); iter.hasNext();) { String leftAlias = alias; ASTPath path = (ASTPath) iter.next(); for(int i = 1; i < path.size(); ++i) { if(path.isCMRField(i)) { final String curPath = path.getPath(i); final String joinAlias = aliasManager.getAlias(curPath); if(joinedAliases.add(joinAlias)) { final JDBCAbstractCMRFieldBridge cmrField = (JDBCAbstractCMRFieldBridge) path.getCMRField(i); final JDBCAbstractEntityBridge joinEntity = (JDBCAbstractEntityBridge) cmrField.getRelatedEntity(); JDBCRelationMetaData relation = cmrField.getMetaData().getRelationMetaData(); String join = (path.innerJoin ? " INNER JOIN " : " LEFT OUTER JOIN "); if(relation.isTableMappingStyle()) { String relTableAlias = aliasManager.getRelationTableAlias(curPath); sql.append(join) .append(cmrField.getQualifiedTableName()) .append(' ') .append(relTableAlias) .append(" ON "); SQLUtil.getRelationTableJoinClause(cmrField, leftAlias, relTableAlias, sql); sql.append(join) .append(joinEntity.getQualifiedTableName()) .append(' ') .append(joinAlias) .append(" ON "); SQLUtil.getRelationTableJoinClause(cmrField.getRelatedCMRField(), joinAlias, relTableAlias, sql); } else { sql.append(join) .append(joinEntity.getQualifiedTableName()) .append(' ') .append(joinAlias) .append(" ON "); SQLUtil.getJoinClause(cmrField, leftAlias, joinAlias, sql); } join(joinAlias, sql); } leftAlias = joinAlias; } } } } private void declareTable(String alias, String table) { identifierToTable.put(alias, table); } private void addLeftJoinPath(ASTPath path) { if(path.size() > 1 && path.isCMRField(1)) { final String identifier = path.getPath(0); final String alias = aliasManager.getAlias(identifier); Map paths = (Map) joinPaths.get(alias); if(paths == null) { paths = new HashMap(); joinPaths.put(alias, paths); } ASTPath oldPath = (ASTPath) paths.put(path, path); if(oldPath != null && oldPath.innerJoin) { path.innerJoin = true; } } } private void addInnerJoinPath(ASTPath path) { if(path.size() > 1 && path.isCMRField(1)) { final String identifier = path.getPath(0); final String alias = aliasManager.getAlias(identifier); Map paths = (Map) joinPaths.get(alias); if(paths == null) { paths = new HashMap(); joinPaths.put(alias, paths); } path.innerJoin = true; paths.put(path, path); } } private Object[] childrenToStringArr(int numChildren, Node node) { Object[] args = new Object[numChildren]; for(int i = 0; i < numChildren; ++i) { args[i] = node.jjtGetChild(i).jjtAccept(this, new StringBuffer()).toString(); } return args; } /** * 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; } private void setTypeFactory(JDBCTypeFactory typeFactory) { this.typeFactory = typeFactory; this.typeMapping = typeFactory.getTypeMapping(); aliasManager = new AliasManager(typeMapping.getAliasHeaderPrefix(), typeMapping.getAliasHeaderSuffix(), typeMapping.getAliasMaxLength()); } 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, JDBCAbstractEntityBridge entity) { Class parameterType = getParameterType(number); Class remoteClass = entity.getRemoteInterface(); Class localClass = entity.getLocalInterface(); 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 reset() { returnType = null; parameterTypes = null; readAhead = null; inputParameters.clear(); selectObject = null; selectManager = null; typeFactory = null; typeMapping = null; aliasManager = null; forceDistinct = false; limitParam = 0; limitValue = 0; offsetParam = 0; offsetValue = 0; leftJoinCMRList.clear(); onFindCMRJoin = null; countCompositePk = false; joinPaths.clear(); identifierToTable.clear(); joinedAliases.clear(); selectDistinct = false; functionJDBCType = null; } }