0001: /*
0002: * JBoss, Home of Professional Open Source.
0003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
0004: * as indicated by the @author tags. See the copyright.txt file in the
0005: * distribution for a full listing of individual contributors.
0006: *
0007: * This is free software; you can redistribute it and/or modify it
0008: * under the terms of the GNU Lesser General Public License as
0009: * published by the Free Software Foundation; either version 2.1 of
0010: * the License, or (at your option) any later version.
0011: *
0012: * This software is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * You should have received a copy of the GNU Lesser General Public
0018: * License along with this software; if not, write to the Free
0019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
0021: */
0022: package org.jboss.ejb.plugins.cmp.jdbc;
0023:
0024: import java.io.StringReader;
0025: import java.util.ArrayList;
0026: import java.util.HashMap;
0027: import java.util.HashSet;
0028: import java.util.Iterator;
0029: import java.util.List;
0030: import java.util.Map;
0031: import java.util.Set;
0032:
0033: import org.jboss.ejb.plugins.cmp.ejbql.ASTAbs;
0034: import org.jboss.ejb.plugins.cmp.ejbql.ASTAbstractSchema;
0035: import org.jboss.ejb.plugins.cmp.ejbql.ASTBooleanLiteral;
0036: import org.jboss.ejb.plugins.cmp.ejbql.ASTCollectionMemberDeclaration;
0037: import org.jboss.ejb.plugins.cmp.ejbql.ASTConcat;
0038: import org.jboss.ejb.plugins.cmp.ejbql.ASTEJBQL;
0039: import org.jboss.ejb.plugins.cmp.ejbql.ASTEntityComparison;
0040: import org.jboss.ejb.plugins.cmp.ejbql.ASTFrom;
0041: import org.jboss.ejb.plugins.cmp.ejbql.ASTIdentifier;
0042: import org.jboss.ejb.plugins.cmp.ejbql.ASTIsEmpty;
0043: import org.jboss.ejb.plugins.cmp.ejbql.ASTLCase;
0044: import org.jboss.ejb.plugins.cmp.ejbql.ASTLength;
0045: import org.jboss.ejb.plugins.cmp.ejbql.ASTLocate;
0046: import org.jboss.ejb.plugins.cmp.ejbql.ASTMemberOf;
0047: import org.jboss.ejb.plugins.cmp.ejbql.ASTNullComparison;
0048: import org.jboss.ejb.plugins.cmp.ejbql.ASTOrderBy;
0049: import org.jboss.ejb.plugins.cmp.ejbql.ASTParameter;
0050: import org.jboss.ejb.plugins.cmp.ejbql.ASTPath;
0051: import org.jboss.ejb.plugins.cmp.ejbql.ASTRangeVariableDeclaration;
0052: import org.jboss.ejb.plugins.cmp.ejbql.ASTSelect;
0053: import org.jboss.ejb.plugins.cmp.ejbql.ASTSqrt;
0054: import org.jboss.ejb.plugins.cmp.ejbql.ASTSubstring;
0055: import org.jboss.ejb.plugins.cmp.ejbql.ASTUCase;
0056: import org.jboss.ejb.plugins.cmp.ejbql.ASTValueClassComparison;
0057: import org.jboss.ejb.plugins.cmp.ejbql.ASTWhere;
0058: import org.jboss.ejb.plugins.cmp.ejbql.BasicVisitor;
0059: import org.jboss.ejb.plugins.cmp.ejbql.Catalog;
0060: import org.jboss.ejb.plugins.cmp.ejbql.EJBQLParser;
0061: import org.jboss.ejb.plugins.cmp.ejbql.EJBQLTypes;
0062: import org.jboss.ejb.plugins.cmp.ejbql.JBossQLParser;
0063: import org.jboss.ejb.plugins.cmp.ejbql.Node;
0064: import org.jboss.ejb.plugins.cmp.ejbql.SimpleNode;
0065: import org.jboss.ejb.plugins.cmp.ejbql.ASTLimitOffset;
0066: import org.jboss.ejb.plugins.cmp.ejbql.ASTCount;
0067: import org.jboss.ejb.plugins.cmp.ejbql.SelectFunction;
0068: import org.jboss.ejb.plugins.cmp.ejbql.ASTExactNumericLiteral;
0069: import org.jboss.ejb.plugins.cmp.ejbql.ASTMax;
0070: import org.jboss.ejb.plugins.cmp.ejbql.ASTMin;
0071: import org.jboss.ejb.plugins.cmp.ejbql.ASTAvg;
0072: import org.jboss.ejb.plugins.cmp.ejbql.ASTSum;
0073: import org.jboss.ejb.plugins.cmp.ejbql.ASTWhereConditionalTerm;
0074: import org.jboss.ejb.plugins.cmp.ejbql.ASTMod;
0075: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMPFieldBridge;
0076: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMRFieldBridge;
0077: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge;
0078: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCFieldBridge;
0079: import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCAbstractEntityBridge;
0080: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCFunctionMappingMetaData;
0081: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCReadAheadMetaData;
0082: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCTypeMappingMetaData;
0083: import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCQueryMetaData;
0084: import org.jboss.ejb.plugins.cmp.bridge.CMPFieldBridge;
0085: import org.jboss.ejb.EntityPersistenceStore;
0086: import org.jboss.deployment.DeploymentException;
0087:
0088: /**
0089: * Compiles EJB-QL and JBossQL into SQL.
0090: *
0091: * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
0092: * @author <a href="mailto:alex@jboss.org">Alex Loubyansky</a>
0093: * @version $Revision: 63343 $
0094: */
0095: public final class JDBCEJBQLCompiler extends BasicVisitor implements
0096: QLCompiler {
0097: // input objects
0098: private final Catalog catalog;
0099: private Class returnType;
0100: private Class[] parameterTypes;
0101: private JDBCReadAheadMetaData readAhead;
0102: private boolean lazyResultSetLoading;
0103:
0104: // alias info
0105: private AliasManager aliasManager;
0106:
0107: // join info
0108: private Set declaredPaths = new HashSet();
0109: private Set ctermJoinPaths = new HashSet();
0110: private Set allJoinPaths = new HashSet();
0111: private Map ctermCollectionMemberJoinPaths = new HashMap();
0112: private Map allCollectionMemberJoinPaths = new HashMap();
0113: private Map ctermLeftJoinPaths = new HashMap();
0114: private Map allLeftJoinPaths = new HashMap();
0115:
0116: // mapping metadata
0117: private JDBCTypeMappingMetaData typeMapping;
0118: private JDBCTypeFactory typeFactory;
0119: private boolean subquerySupported;
0120:
0121: // output objects
0122: private boolean forceDistinct;
0123: private String sql;
0124: private int offsetParam;
0125: private int offsetValue;
0126: private int limitParam;
0127: private int limitValue;
0128: private JDBCStoreManager selectManager;
0129: private Object selectObject;
0130: private List inputParameters = new ArrayList();
0131:
0132: /**
0133: * deep read ahead for cmrs
0134: */
0135: private List leftJoinCMRList = new ArrayList();
0136: private StringBuffer onFindCMRJoin;
0137:
0138: private boolean countCompositePk;
0139: private String selectAlias;
0140: private boolean selectDistinct;
0141:
0142: public JDBCEJBQLCompiler(Catalog catalog) {
0143: this .catalog = catalog;
0144: }
0145:
0146: public void compileEJBQL(String ejbql, Class returnType,
0147: Class[] parameterTypes, JDBCQueryMetaData metadata)
0148: throws Exception {
0149: // reset all state variables
0150: reset();
0151:
0152: // set input arguemts
0153: this .returnType = returnType;
0154: this .parameterTypes = parameterTypes;
0155: this .readAhead = metadata.getReadAhead();
0156: this .lazyResultSetLoading = metadata.isLazyResultSetLoading();
0157:
0158: // get the parser
0159: EJBQLParser parser = new EJBQLParser(new StringReader(
0160: SQLUtil.EMPTY_STRING));
0161:
0162: try {
0163: // parse the ejbql into an abstract sytax tree
0164: ASTEJBQL ejbqlNode;
0165: ejbqlNode = parser.parse(catalog, parameterTypes, ejbql);
0166:
0167: // translate to sql
0168: sql = ejbqlNode.jjtAccept(this , new StringBuffer())
0169: .toString();
0170: } catch (Exception e) {
0171: // if there is a problem reset the state before exiting
0172: reset();
0173: throw e;
0174: } catch (Error e) {
0175: // lame javacc lexer throws Errors
0176: reset();
0177: throw e;
0178: }
0179: }
0180:
0181: public void compileJBossQL(String ejbql, Class returnType,
0182: Class[] parameterTypes, JDBCQueryMetaData metadata)
0183: throws Exception {
0184: // reset all state variables
0185: reset();
0186:
0187: // set input arguemts
0188: this .returnType = returnType;
0189: this .parameterTypes = parameterTypes;
0190: this .readAhead = metadata.getReadAhead();
0191: this .lazyResultSetLoading = metadata.isLazyResultSetLoading();
0192:
0193: // get the parser
0194: JBossQLParser parser = new JBossQLParser(new StringReader(
0195: SQLUtil.EMPTY_STRING));
0196:
0197: try {
0198: // parse the ejbql into an abstract sytax tree
0199: ASTEJBQL ejbqlNode;
0200: ejbqlNode = parser.parse(catalog, parameterTypes, ejbql);
0201:
0202: // translate to sql
0203: sql = ejbqlNode.jjtAccept(this , new StringBuffer())
0204: .toString();
0205: } catch (Exception e) {
0206: // if there is a problem reset the state before exiting
0207: reset();
0208: throw e;
0209: } catch (Error e) {
0210: // lame javacc lexer throws Errors
0211: reset();
0212: throw e;
0213: }
0214: }
0215:
0216: private void reset() {
0217: returnType = null;
0218: parameterTypes = null;
0219: readAhead = null;
0220: inputParameters.clear();
0221: declaredPaths.clear();
0222: clearPerTermJoinPaths();
0223: allJoinPaths.clear();
0224: allCollectionMemberJoinPaths.clear();
0225: allLeftJoinPaths.clear();
0226: selectObject = null;
0227: selectManager = null;
0228: typeFactory = null;
0229: typeMapping = null;
0230: aliasManager = null;
0231: subquerySupported = true;
0232: forceDistinct = false;
0233: limitParam = 0;
0234: limitValue = 0;
0235: offsetParam = 0;
0236: offsetValue = 0;
0237: leftJoinCMRList.clear();
0238: onFindCMRJoin = null;
0239: countCompositePk = false;
0240: selectAlias = null;
0241: selectDistinct = false;
0242: }
0243:
0244: public String getSQL() {
0245: return sql;
0246: }
0247:
0248: public int getOffsetValue() {
0249: return offsetValue;
0250: }
0251:
0252: public int getOffsetParam() {
0253: return offsetParam;
0254: }
0255:
0256: public int getLimitValue() {
0257: return limitValue;
0258: }
0259:
0260: public int getLimitParam() {
0261: return limitParam;
0262: }
0263:
0264: public boolean isSelectEntity() {
0265: return selectObject instanceof JDBCEntityBridge;
0266: }
0267:
0268: public JDBCAbstractEntityBridge getSelectEntity() {
0269: return (JDBCAbstractEntityBridge) selectObject;
0270: }
0271:
0272: public boolean isSelectField() {
0273: return selectObject instanceof JDBCCMPFieldBridge;
0274: }
0275:
0276: public JDBCFieldBridge getSelectField() {
0277: return (JDBCCMPFieldBridge) selectObject;
0278: }
0279:
0280: public SelectFunction getSelectFunction() {
0281: return (SelectFunction) selectObject;
0282: }
0283:
0284: public EntityPersistenceStore getStoreManager() {
0285: return selectManager;
0286: }
0287:
0288: public List getInputParameters() {
0289: return inputParameters;
0290: }
0291:
0292: public List getLeftJoinCMRList() {
0293: return leftJoinCMRList;
0294: }
0295:
0296: public boolean isSelectDistinct() {
0297: return selectDistinct;
0298: }
0299:
0300: public Object visit(SimpleNode node, Object data) {
0301: throw new RuntimeException(
0302: "Internal error: Found unknown node type in "
0303: + "EJB-QL abstract syntax tree: node=" + node);
0304: }
0305:
0306: private void setTypeFactory(JDBCTypeFactory typeFactory) {
0307: this .typeFactory = typeFactory;
0308: this .typeMapping = typeFactory.getTypeMapping();
0309: aliasManager = new AliasManager(typeMapping
0310: .getAliasHeaderPrefix(), typeMapping
0311: .getAliasHeaderSuffix(), typeMapping
0312: .getAliasMaxLength());
0313: subquerySupported = typeMapping.isSubquerySupported();
0314: }
0315:
0316: private Class getParameterType(int index) {
0317: int zeroBasedIndex = index - 1;
0318: Class[] params = parameterTypes;
0319: if (zeroBasedIndex < params.length) {
0320: return params[zeroBasedIndex];
0321: }
0322: return null;
0323: }
0324:
0325: // verify that parameter is the same type as the entity
0326: private void verifyParameterEntityType(int number,
0327: JDBCEntityBridge entity) {
0328: Class parameterType = getParameterType(number);
0329: Class remoteClass = entity.getMetaData().getRemoteClass();
0330: Class localClass = entity.getMetaData().getLocalClass();
0331: if ((localClass == null || !localClass
0332: .isAssignableFrom(parameterType))
0333: && (remoteClass == null || !remoteClass
0334: .isAssignableFrom(parameterType))) {
0335:
0336: throw new IllegalStateException("Only like types can be "
0337: + "compared: from entity=" + entity.getEntityName()
0338: + " to parameter type=" + parameterType);
0339: }
0340: }
0341:
0342: private void compareEntity(boolean not, Node fromNode, Node toNode,
0343: StringBuffer buf) {
0344: buf.append('(');
0345: if (not) {
0346: buf.append(SQLUtil.NOT).append('(');
0347: }
0348:
0349: String fromAlias;
0350: JDBCEntityBridge fromEntity;
0351: ASTPath fromPath = (ASTPath) fromNode;
0352: addJoinPath(fromPath);
0353: fromAlias = aliasManager.getAlias(fromPath.getPath());
0354: fromEntity = (JDBCEntityBridge) fromPath.getEntity();
0355:
0356: if (toNode instanceof ASTParameter) {
0357: ASTParameter toParam = (ASTParameter) toNode;
0358:
0359: // can only compare like kind entities
0360: verifyParameterEntityType(toParam.number, fromEntity);
0361:
0362: inputParameters.addAll(QueryParameter.createParameters(
0363: toParam.number - 1, fromEntity));
0364:
0365: SQLUtil.getWhereClause(fromEntity.getPrimaryKeyFields(),
0366: fromAlias, buf);
0367: } else {
0368: String toAlias;
0369: JDBCEntityBridge toEntity;
0370: ASTPath toPath = (ASTPath) toNode;
0371: addJoinPath(toPath);
0372: toAlias = aliasManager.getAlias(toPath.getPath());
0373: toEntity = (JDBCEntityBridge) toPath.getEntity();
0374:
0375: // can only compare like kind entities
0376: if (!fromEntity.equals(toEntity)) {
0377: throw new IllegalStateException(
0378: "Only like types can be "
0379: + "compared: from entity="
0380: + fromEntity.getEntityName()
0381: + " to entity="
0382: + toEntity.getEntityName());
0383: }
0384:
0385: SQLUtil.getSelfCompareWhereClause(fromEntity
0386: .getPrimaryKeyFields(), fromAlias, toAlias, buf);
0387: }
0388:
0389: if (not) {
0390: buf.append(')');
0391: }
0392: buf.append(')');
0393: }
0394:
0395: private void existsClause(ASTPath path, StringBuffer buf,
0396: boolean not) {
0397: if (!path.isCMRField()) {
0398: throw new IllegalArgumentException(
0399: "path must be a cmr field");
0400: }
0401:
0402: JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) path
0403: .getCMRField();
0404: String pathStr = path.getPath(path.size() - 2);
0405: String parentAlias = aliasManager.getAlias(pathStr);
0406:
0407: // if exists is not supported we use a left join and is null
0408: if (!subquerySupported) {
0409: // add the path to the list of paths to left join
0410: addLeftJoinPath(pathStr, path);
0411: forceDistinct = true;
0412:
0413: addJoinPath(path);
0414:
0415: if (cmrField.getRelationMetaData()
0416: .isForeignKeyMappingStyle()) {
0417: JDBCEntityBridge childEntity = (JDBCEntityBridge) cmrField
0418: .getRelatedEntity();
0419: String childAlias = aliasManager.getAlias(path
0420: .getPath());
0421: SQLUtil.getIsNullClause(!not, childEntity
0422: .getPrimaryKeyFields(), childAlias, buf);
0423: } else {
0424: String relationTableAlias = aliasManager
0425: .getRelationTableAlias(path.getPath());
0426: SQLUtil.getIsNullClause(!not, cmrField
0427: .getTableKeyFields(), relationTableAlias, buf);
0428: }
0429: return;
0430: }
0431:
0432: if (not) {
0433: buf.append(SQLUtil.NOT);
0434: }
0435: buf.append(SQLUtil.EXISTS).append('(');
0436:
0437: if (cmrField.getRelationMetaData().isForeignKeyMappingStyle()) {
0438: JDBCEntityBridge childEntity = (JDBCEntityBridge) cmrField
0439: .getRelatedEntity();
0440: String childAlias = aliasManager.getAlias(path.getPath());
0441:
0442: buf.append(SQLUtil.SELECT);
0443:
0444: SQLUtil.getColumnNamesClause(
0445: childEntity.getPrimaryKeyFields(), childAlias, buf)
0446: .append(SQLUtil.FROM).append(
0447: childEntity.getQualifiedTableName())
0448: .append(' ').append(childAlias).append(
0449: SQLUtil.WHERE);
0450: SQLUtil.getJoinClause(cmrField, parentAlias, childAlias,
0451: buf);
0452: } else {
0453: String relationTableAlias = aliasManager
0454: .getRelationTableAlias(path.getPath());
0455: buf.append(SQLUtil.SELECT);
0456: SQLUtil.getColumnNamesClause(cmrField.getTableKeyFields(),
0457: relationTableAlias, buf).append(SQLUtil.FROM)
0458: .append(cmrField.getQualifiedTableName()).append(
0459: ' ').append(relationTableAlias).append(
0460: SQLUtil.WHERE);
0461: SQLUtil.getRelationTableJoinClause(cmrField, parentAlias,
0462: relationTableAlias, buf);
0463: }
0464:
0465: buf.append(')');
0466: }
0467:
0468: public Object visit(ASTEJBQL node, Object data) {
0469: Node selectNode = node.jjtGetChild(0);
0470: Node fromNode = node.jjtGetChild(1);
0471: Node whereNode = null;
0472: Node orderByNode = null;
0473: Node limitNode = null;
0474:
0475: for (int childNode = 2; childNode < node.jjtGetNumChildren(); childNode++) {
0476: Node temp = node.jjtGetChild(childNode);
0477: if (temp instanceof ASTWhere) {
0478: whereNode = temp;
0479: } else if (temp instanceof ASTOrderBy) {
0480: orderByNode = temp;
0481: } else if (temp instanceof ASTLimitOffset) {
0482: limitNode = temp;
0483: }
0484: }
0485:
0486: // translate select and add it to the buffer
0487: StringBuffer select = new StringBuffer();
0488: selectNode.jjtAccept(this , select);
0489:
0490: // conditional term paths from the where clause are treated separately from those
0491: // in select, from and order by.
0492: // TODO come up with a nicer treatment implementation.
0493: Set selectJoinPaths = new HashSet(ctermJoinPaths);
0494: Map selectCollectionMemberJoinPaths = new HashMap(
0495: ctermCollectionMemberJoinPaths);
0496: Map selectLeftJoinPaths = new HashMap(ctermLeftJoinPaths);
0497:
0498: // translate where and save results to append later
0499: StringBuffer where = new StringBuffer();
0500: if (whereNode != null) {
0501: whereNode.jjtAccept(this , where);
0502: }
0503:
0504: // reassign conditional term paths and add paths from order by and from to select paths
0505: ctermJoinPaths = selectJoinPaths;
0506: ctermCollectionMemberJoinPaths = selectCollectionMemberJoinPaths;
0507: ctermLeftJoinPaths = selectLeftJoinPaths;
0508:
0509: // translate order by and save results to append later
0510: StringBuffer orderBy = new StringBuffer();
0511: if (orderByNode != null) {
0512: orderByNode.jjtAccept(this , orderBy);
0513:
0514: // hack alert - this should use the visitor approach
0515: for (int i = 0; i < orderByNode.jjtGetNumChildren(); i++) {
0516: Node orderByPath = orderByNode.jjtGetChild(i);
0517: ASTPath path = (ASTPath) orderByPath.jjtGetChild(0);
0518: if (!isSelected(path)) {
0519: select.append(SQLUtil.COMMA);
0520: path.jjtAccept(this , select);
0521: }
0522: }
0523: }
0524:
0525: if (limitNode != null) {
0526: limitNode.jjtAccept(this , null);
0527: }
0528:
0529: StringBuffer from = new StringBuffer(50);
0530: fromNode.jjtAccept(this , from);
0531:
0532: StringBuffer fromThetaJoin = new StringBuffer();
0533: createThetaJoin(fromThetaJoin);
0534:
0535: if (where.length() != 0 && fromThetaJoin.length() != 0) {
0536: where.insert(0, '(').append(')').append(SQLUtil.AND)
0537: .append(fromThetaJoin);
0538: } else if (fromThetaJoin.length() != 0) {
0539: where.append(fromThetaJoin.toString());
0540: }
0541:
0542: selectDistinct = isDistinct(selectNode);
0543:
0544: // select size
0545: if (lazyResultSetLoading) {
0546: StringBuffer buf = new StringBuffer(200);
0547: if (isSelectEntity()) {
0548: final JDBCFieldBridge[] pkFields = getSelectEntity()
0549: .getPrimaryKeyFields();
0550: if (pkFields.length == 1) {
0551: buf.append('(').append(SQLUtil.SELECT).append(
0552: "count(");
0553: if (selectDistinct) {
0554: buf.append(SQLUtil.DISTINCT);
0555: }
0556: SQLUtil.getColumnNamesClause(pkFields, selectAlias,
0557: buf);
0558: buf.append(')').append(SQLUtil.FROM);
0559: buf.append(from);
0560: if (where.length() > 0) {
0561: buf.append(SQLUtil.WHERE).append(where);
0562: }
0563: buf.append("), ");
0564: select.insert(0, buf);
0565: } else {
0566: buf.append('(').append(SQLUtil.SELECT).append(
0567: "count(*)").append(SQLUtil.FROM)
0568: .append('(').append(SQLUtil.SELECT);
0569:
0570: if (selectDistinct) {
0571: buf.append(SQLUtil.DISTINCT);
0572: }
0573:
0574: SQLUtil.getColumnNamesClause(pkFields, selectAlias,
0575: buf);
0576: buf.append(SQLUtil.FROM).append(from);
0577:
0578: if (where.length() > 0) {
0579: buf.append(SQLUtil.WHERE).append(where);
0580: }
0581: buf.append(") t_count), ");
0582: select.insert(0, buf);
0583: }
0584: } else if (isSelectField()) {
0585: buf.append('(').append(SQLUtil.SELECT).append("count(");
0586: if (selectDistinct) {
0587: buf.append(SQLUtil.DISTINCT);
0588: }
0589: buf.append(select).append(')').append(SQLUtil.FROM);
0590: buf.append(from);
0591: if (where.length() > 0) {
0592: buf.append(SQLUtil.WHERE).append(where);
0593: }
0594: buf.append("), ");
0595: select.insert(0, buf);
0596: }
0597: }
0598:
0599: // distinct
0600: if (selectDistinct) {
0601: select.insert(0, SQLUtil.DISTINCT);
0602: }
0603:
0604: StringBuffer buf = (StringBuffer) data;
0605: if (selectManager.getMetaData().hasRowLocking()) {
0606: JDBCFunctionMappingMetaData rowLockingTemplate = typeMapping
0607: .getRowLockingTemplate();
0608: Object args[] = new Object[] { select, from,
0609: where.length() == 0 ? null : where,
0610: orderBy.length() == 0 ? null : orderBy };
0611: rowLockingTemplate.getFunctionSql(args, buf);
0612: } else {
0613: buf.append(SQLUtil.SELECT).append(select).append(
0614: SQLUtil.FROM).append(from);
0615:
0616: if (where.length() > 0) {
0617: buf.append(SQLUtil.WHERE).append(where);
0618: }
0619:
0620: if (orderBy.length() != 0) {
0621: buf.append(SQLUtil.ORDERBY).append(orderBy);
0622: }
0623: }
0624:
0625: // todo: ...
0626: if (countCompositePk) {
0627: buf.insert(0, "SELECT COUNT(*) FROM (").append(") t_count");
0628: }
0629:
0630: return buf;
0631: }
0632:
0633: public Object visit(ASTFrom node, Object data) {
0634: StringBuffer buf = (StringBuffer) data;
0635:
0636: node.jjtGetChild(0).jjtAccept(this , buf);
0637: for (int i = 1; i < node.jjtGetNumChildren(); i++) {
0638: buf.append(SQLUtil.COMMA);
0639: node.jjtGetChild(i).jjtAccept(this , buf);
0640: }
0641:
0642: // add all the additional path tables
0643: if (!allJoinPaths.isEmpty()) {
0644: for (Iterator iter = allJoinPaths.iterator(); iter
0645: .hasNext();) {
0646: ASTPath path = (ASTPath) iter.next();
0647: for (int i = 0; i < path.size(); i++) {
0648: declareTables(path, i, buf);
0649: }
0650: }
0651: }
0652:
0653: // add all parent paths for collection member join paths
0654: if (!allCollectionMemberJoinPaths.isEmpty()) {
0655: for (Iterator iter = allCollectionMemberJoinPaths.values()
0656: .iterator(); iter.hasNext();) {
0657: ASTPath path = (ASTPath) iter.next();
0658: // don't declare the last one as the first path was left joined
0659: for (int i = 0; i < path.size() - 1; i++) {
0660: declareTables(path, i, buf);
0661: }
0662: }
0663: }
0664:
0665: // get all the left joined paths
0666: if (!allLeftJoinPaths.isEmpty()) {
0667: Set allLeftJoins = new HashSet();
0668: for (Iterator iter = allLeftJoinPaths.values().iterator(); iter
0669: .hasNext();) {
0670: allLeftJoins.addAll((Set) iter.next());
0671: }
0672:
0673: // add all parent paths for left joins
0674: for (Iterator iter = allLeftJoins.iterator(); iter
0675: .hasNext();) {
0676: ASTPath path = (ASTPath) iter.next();
0677: // don't declare the last one as the first path was left joined
0678: for (int i = 0; i < path.size() - 1; i++) {
0679: declareTables(path, i, buf);
0680: }
0681: }
0682: }
0683:
0684: return buf;
0685: }
0686:
0687: private void declareTables(ASTPath path, int i, StringBuffer buf) {
0688: if (!path.isCMRField(i)
0689: || declaredPaths.contains(path.getPath(i))) {
0690: return;
0691: }
0692:
0693: JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) path
0694: .getCMRField(i);
0695: JDBCEntityBridge entity = (JDBCEntityBridge) path.getEntity(i);
0696:
0697: buf.append(SQLUtil.COMMA)
0698: .append(entity.getQualifiedTableName()).append(' ')
0699: .append(aliasManager.getAlias(path.getPath(i)));
0700: leftJoins(path.getPath(i), buf);
0701:
0702: if (cmrField.getRelationMetaData().isTableMappingStyle()) {
0703: String relationTableAlias = aliasManager
0704: .getRelationTableAlias(path.getPath(i));
0705: buf.append(SQLUtil.COMMA).append(
0706: cmrField.getQualifiedTableName()).append(' ')
0707: .append(relationTableAlias);
0708: }
0709:
0710: declaredPaths.add(path.getPath(i));
0711: }
0712:
0713: private void leftJoins(String parentPath, StringBuffer buf) {
0714: Set paths = (Set) ctermLeftJoinPaths.get(parentPath);
0715: if (subquerySupported || paths == null) {
0716: return;
0717: }
0718:
0719: for (Iterator iter = paths.iterator(); iter.hasNext();) {
0720: ASTPath path = (ASTPath) iter.next();
0721:
0722: JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) path
0723: .getCMRField();
0724: String parentAlias = aliasManager.getAlias(parentPath);
0725:
0726: if (cmrField.getRelationMetaData()
0727: .isForeignKeyMappingStyle()) {
0728: JDBCEntityBridge childEntity = (JDBCEntityBridge) cmrField
0729: .getRelatedEntity();
0730: String childAlias = aliasManager.getAlias(path
0731: .getPath());
0732:
0733: buf.append(SQLUtil.LEFT_JOIN).append(
0734: childEntity.getQualifiedTableName())
0735: .append(' ').append(childAlias).append(
0736: SQLUtil.ON);
0737: SQLUtil.getJoinClause(cmrField, parentAlias,
0738: childAlias, buf);
0739: } else {
0740: String relationTableAlias = aliasManager
0741: .getRelationTableAlias(path.getPath());
0742: buf.append(SQLUtil.LEFT_JOIN).append(
0743: cmrField.getQualifiedTableName()).append(' ')
0744: .append(relationTableAlias).append(SQLUtil.ON);
0745: SQLUtil.getRelationTableJoinClause(cmrField,
0746: parentAlias, relationTableAlias, buf);
0747: }
0748: }
0749: }
0750:
0751: private void createThetaJoin(StringBuffer buf) {
0752: Set joinedAliases = new HashSet();
0753: // add all the additional path tables
0754: if (!ctermJoinPaths.isEmpty()) {
0755: for (Iterator iter = ctermJoinPaths.iterator(); iter
0756: .hasNext();) {
0757: ASTPath path = (ASTPath) iter.next();
0758: for (int i = 0; i < path.size(); i++) {
0759: createThetaJoin(path, i, joinedAliases, buf);
0760: }
0761: }
0762: }
0763:
0764: // add all the collection member path tables
0765: if (!ctermCollectionMemberJoinPaths.isEmpty()) {
0766: for (Iterator iter = ctermCollectionMemberJoinPaths
0767: .entrySet().iterator(); iter.hasNext();) {
0768: Map.Entry entry = (Map.Entry) iter.next();
0769: String childAlias = (String) entry.getKey();
0770: ASTPath path = (ASTPath) entry.getValue();
0771:
0772: // join the memeber path
0773: createThetaJoin(path, path.size() - 1, joinedAliases,
0774: childAlias, buf);
0775:
0776: // join the memeber path parents
0777: for (int i = 0; i < path.size() - 1; i++) {
0778: createThetaJoin(path, i, joinedAliases, buf);
0779: }
0780: }
0781: }
0782:
0783: // get all the left joined paths
0784: if (!ctermLeftJoinPaths.isEmpty()) {
0785: Set allLeftJoins = new HashSet();
0786: for (Iterator iter = ctermLeftJoinPaths.values().iterator(); iter
0787: .hasNext();) {
0788: allLeftJoins.addAll((Set) iter.next());
0789: }
0790:
0791: // add all parent paths for left joins
0792: for (Iterator iter = allLeftJoins.iterator(); iter
0793: .hasNext();) {
0794: ASTPath path = (ASTPath) iter.next();
0795: // don't declare the last one as the first path was left joined
0796: for (int i = 0; i < path.size() - 1; i++) {
0797: createThetaJoin(path, i, joinedAliases, buf);
0798: }
0799: }
0800: }
0801: }
0802:
0803: private void createThetaJoin(ASTPath path, int i,
0804: Set joinedAliases, StringBuffer buf) {
0805: String childAlias = aliasManager.getAlias(path.getPath(i));
0806: createThetaJoin(path, i, joinedAliases, childAlias, buf);
0807: }
0808:
0809: private void createThetaJoin(ASTPath path, int i,
0810: Set joinedAliases, String childAlias, StringBuffer buf) {
0811: if (!path.isCMRField(i) || joinedAliases.contains(childAlias)) {
0812: return;
0813: }
0814:
0815: JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) path
0816: .getCMRField(i);
0817: String parentAlias = aliasManager.getAlias(path.getPath(i - 1));
0818:
0819: if (joinedAliases.size() > 0) {
0820: buf.append(SQLUtil.AND);
0821: }
0822:
0823: if (cmrField.getRelationMetaData().isForeignKeyMappingStyle()) {
0824: SQLUtil.getJoinClause(cmrField, parentAlias, childAlias,
0825: buf);
0826: } else {
0827: String relationTableAlias = aliasManager
0828: .getRelationTableAlias(path.getPath(i));
0829:
0830: // parent to relation table
0831: SQLUtil.getRelationTableJoinClause(cmrField, parentAlias,
0832: relationTableAlias, buf).append(SQLUtil.AND);
0833: // child to relation table
0834: SQLUtil.getRelationTableJoinClause(cmrField
0835: .getRelatedCMRField(), childAlias,
0836: relationTableAlias, buf);
0837: }
0838:
0839: joinedAliases.add(childAlias);
0840: }
0841:
0842: public Object visit(ASTCollectionMemberDeclaration node, Object data) {
0843: StringBuffer buf = (StringBuffer) data;
0844:
0845: // first arg is a collection valued path
0846: ASTPath path = (ASTPath) node.jjtGetChild(0);
0847:
0848: // add this path to the list of declared paths
0849: declaredPaths.add(path.getPath());
0850:
0851: // get the entity at the end of this path
0852: JDBCEntityBridge entity = (JDBCEntityBridge) path.getEntity();
0853:
0854: // second arg is the identifier
0855: ASTIdentifier id = (ASTIdentifier) node.jjtGetChild(1);
0856:
0857: // get the alias
0858: String alias = aliasManager.getAlias(id.identifier);
0859:
0860: // add this path to the list of join paths so parent paths will be joined
0861: addCollectionMemberJoinPath(alias, path);
0862:
0863: // declare the alias mapping
0864: aliasManager.addAlias(path.getPath(), alias);
0865:
0866: buf.append(entity.getQualifiedTableName());
0867: buf.append(' ');
0868: buf.append(alias);
0869: leftJoins(path.getPath(), buf);
0870:
0871: if (onFindCMRJoin != null && alias.equals(selectAlias)) {
0872: buf.append(onFindCMRJoin);
0873: onFindCMRJoin = null;
0874: }
0875:
0876: // add the relation-table
0877: JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) path
0878: .getCMRField();
0879: if (cmrField.getRelationMetaData().isTableMappingStyle()) {
0880: String relationTableAlias = aliasManager
0881: .getRelationTableAlias(path.getPath());
0882: buf.append(SQLUtil.COMMA).append(
0883: cmrField.getQualifiedTableName()).append(' ')
0884: .append(relationTableAlias);
0885: }
0886:
0887: return buf;
0888: }
0889:
0890: public Object visit(ASTRangeVariableDeclaration node, Object data) {
0891: StringBuffer buf = (StringBuffer) data;
0892:
0893: ASTAbstractSchema schema = (ASTAbstractSchema) node
0894: .jjtGetChild(0);
0895: JDBCEntityBridge entity = (JDBCEntityBridge) schema.entity;
0896: ASTIdentifier id = (ASTIdentifier) node.jjtGetChild(1);
0897:
0898: String alias = aliasManager.getAlias(id.identifier);
0899: buf.append(entity.getQualifiedTableName()).append(' ').append(
0900: alias);
0901: leftJoins(id.identifier, buf);
0902:
0903: if (onFindCMRJoin != null && alias.equals(selectAlias)) {
0904: buf.append(onFindCMRJoin);
0905: onFindCMRJoin = null;
0906: }
0907:
0908: return buf;
0909: }
0910:
0911: public Object visit(ASTSelect node, Object data) {
0912: StringBuffer buf = (StringBuffer) data;
0913:
0914: Node child0 = node.jjtGetChild(0);
0915: ASTPath path;
0916: if (child0 instanceof ASTPath) {
0917: path = (ASTPath) child0;
0918:
0919: if (path.isCMPField()) {
0920: // set the select object
0921: JDBCCMPFieldBridge selectField = (JDBCCMPFieldBridge) path
0922: .getCMPField();
0923: selectManager = (JDBCStoreManager) selectField
0924: .getManager();
0925: selectObject = selectField;
0926: setTypeFactory(selectManager.getJDBCTypeFactory());
0927:
0928: addJoinPath(path);
0929: selectAlias = aliasManager.getAlias(path.getPath(path
0930: .size() - 2));
0931: SQLUtil.getColumnNamesClause(selectField, selectAlias,
0932: buf);
0933: } else {
0934: // set the select object
0935: JDBCEntityBridge selectEntity = (JDBCEntityBridge) path
0936: .getEntity();
0937: selectManager = (JDBCStoreManager) selectEntity
0938: .getManager();
0939: selectObject = selectEntity;
0940: setTypeFactory(selectManager.getJDBCTypeFactory());
0941: selectEntity(path, buf);
0942: }
0943: } else {
0944: // the function should take a path expresion as a parameter
0945: path = getPathFromChildren(child0);
0946:
0947: if (path == null) {
0948: throw new IllegalStateException(
0949: "The function in SELECT clause does not contain a path expression.");
0950: }
0951:
0952: if (path.isCMPField()) {
0953: JDBCCMPFieldBridge selectField = (JDBCCMPFieldBridge) path
0954: .getCMPField();
0955: selectManager = (JDBCStoreManager) selectField
0956: .getManager();
0957: } else if (path.isCMRField()) {
0958: JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) path
0959: .getCMRField();
0960: selectManager = (JDBCStoreManager) cmrField.getEntity()
0961: .getManager();
0962: addJoinPath(path);
0963: } else {
0964: final JDBCEntityBridge entity = (JDBCEntityBridge) path
0965: .getEntity();
0966: selectManager = (JDBCStoreManager) entity.getManager();
0967: addJoinPath(path);
0968: }
0969:
0970: setTypeFactory(selectManager.getJDBCTypeFactory());
0971: selectObject = child0;
0972: child0.jjtAccept(this , buf);
0973: }
0974:
0975: return buf;
0976: }
0977:
0978: /**
0979: * Generates where clause without the "WHERE" keyword.
0980: */
0981: public Object visit(ASTWhere node, Object data) {
0982: node.jjtGetChild(0).jjtAccept(this , data);
0983: return data;
0984: }
0985:
0986: public Object visit(ASTNullComparison node, Object data) {
0987: StringBuffer buf = (StringBuffer) data;
0988:
0989: final Node child0 = node.jjtGetChild(0);
0990: if (child0 instanceof ASTPath) {
0991: ASTPath path = (ASTPath) child0;
0992:
0993: if (path.isCMRField()) {
0994: JDBCCMRFieldBridge cmrField = (JDBCCMRFieldBridge) path
0995: .getCMRField();
0996: if (cmrField.getRelationMetaData()
0997: .isTableMappingStyle()) {
0998: existsClause(path, buf, !node.not);
0999: return buf;
1000: }
1001: }
1002:
1003: String alias = aliasManager.getAlias(path.getPath(path
1004: .size() - 2));
1005: JDBCFieldBridge field = (JDBCFieldBridge) path.getField();
1006:
1007: // if jdbc type is null then it should be a cmr field in
1008: // a one-to-one mapping that isn't a foreign key.
1009: // handle it the way the IS EMPTY on the one side of one-to-many
1010: // relationship is handled
1011: if (field.getJDBCType() == null) {
1012: existsClause(path, buf, !node.not);
1013: return buf;
1014: }
1015:
1016: // check the path for cmr fields and add them to join paths
1017: if (path.fieldList.size() > 2) {
1018: for (int i = 0; i < path.fieldList.size(); ++i) {
1019: Object pathEl = path.fieldList.get(i);
1020: if (pathEl instanceof JDBCCMRFieldBridge) {
1021: addJoinPath(path);
1022: break;
1023: }
1024: }
1025: }
1026:
1027: buf = SQLUtil.getIsNullClause(node.not, field, alias, buf);
1028: } else if (child0 instanceof ASTParameter) {
1029: ASTParameter param = (ASTParameter) child0;
1030: Class type = getParameterType(param.number);
1031:
1032: QueryParameter queryParam = new QueryParameter(
1033: param.number - 1, typeFactory.getJDBCType(type));
1034: inputParameters.add(queryParam);
1035:
1036: buf.append("? IS ");
1037: if (node.not) {
1038: buf.append(SQLUtil.NOT);
1039: }
1040: buf.append(SQLUtil.NULL);
1041: } else {
1042: throw new IllegalStateException(
1043: "Unexpected node in IS NULL clause: " + node);
1044: }
1045:
1046: return buf;
1047: }
1048:
1049: public Object visit(ASTIsEmpty node, Object data) {
1050: StringBuffer buf = (StringBuffer) data;
1051: ASTPath path = (ASTPath) node.jjtGetChild(0);
1052:
1053: existsClause(path, buf, !node.not);
1054: return buf;
1055: }
1056:
1057: /**
1058: * Compare entity
1059: */
1060: public Object visit(ASTMemberOf node, Object data) {
1061: StringBuffer buf = (StringBuffer) data;
1062:
1063: // setup compare to vars first, so we can compre types in from vars
1064: ASTPath toPath = (ASTPath) node.jjtGetChild(1);
1065:
1066: JDBCCMRFieldBridge toCMRField = (JDBCCMRFieldBridge) toPath
1067: .getCMRField();
1068:
1069: JDBCEntityBridge toChildEntity = (JDBCEntityBridge) toPath
1070: .getEntity();
1071:
1072: String pathStr = toPath.getPath(toPath.size() - 2);
1073: String toParentAlias = aliasManager.getAlias(pathStr);
1074: String toChildAlias = aliasManager.getAlias(toPath.getPath());
1075: String relationTableAlias = null;
1076: if (toCMRField.getRelationMetaData().isTableMappingStyle()) {
1077: relationTableAlias = aliasManager
1078: .getRelationTableAlias(toPath.getPath());
1079: }
1080:
1081: // setup from variables
1082: String fromAlias = null;
1083: int fromParamNumber = -1;
1084: if (node.jjtGetChild(0) instanceof ASTParameter) {
1085: ASTParameter fromParam = (ASTParameter) node.jjtGetChild(0);
1086:
1087: // can only compare like kind entities
1088: verifyParameterEntityType(fromParam.number, toChildEntity);
1089:
1090: fromParamNumber = fromParam.number;
1091: } else {
1092: ASTPath fromPath = (ASTPath) node.jjtGetChild(0);
1093: addJoinPath(fromPath);
1094:
1095: JDBCEntityBridge fromEntity = (JDBCEntityBridge) fromPath
1096: .getEntity();
1097: fromAlias = aliasManager.getAlias(fromPath.getPath());
1098:
1099: // can only compare like kind entities
1100: if (!fromEntity.equals(toChildEntity)) {
1101: throw new IllegalStateException(
1102: "Only like types can be "
1103: + "compared: from entity="
1104: + fromEntity.getEntityName()
1105: + " to entity="
1106: + toChildEntity.getEntityName());
1107: }
1108: }
1109:
1110: // add the path to the list of paths to left join
1111: addLeftJoinPath(pathStr, toPath);
1112:
1113: // first part makes toChild not in toParent.child
1114: if (!subquerySupported) {
1115: addJoinPath(toPath);
1116:
1117: // subquery not supported; use a left join and is not null
1118: if (node.not) {
1119: buf.append(SQLUtil.NOT);
1120: }
1121: buf.append('(');
1122:
1123: if (relationTableAlias == null) {
1124: SQLUtil.getIsNullClause(true, toChildEntity
1125: .getPrimaryKeyFields(), toChildAlias, buf);
1126: } else {
1127: SQLUtil.getIsNullClause(true, toCMRField
1128: .getTableKeyFields(), relationTableAlias, buf);
1129: }
1130: } else {
1131: // subquery supported; use exists subquery
1132: if (node.not) {
1133: buf.append(SQLUtil.NOT);
1134: }
1135:
1136: buf.append(SQLUtil.EXISTS).append('(');
1137:
1138: if (relationTableAlias == null) {
1139: buf.append(SQLUtil.SELECT);
1140: SQLUtil.getColumnNamesClause(
1141: toChildEntity.getPrimaryKeyFields(),
1142: toChildAlias, buf).append(SQLUtil.FROM).append(
1143: toChildEntity.getQualifiedTableName()).append(
1144: ' ').append(toChildAlias).append(SQLUtil.WHERE);
1145: SQLUtil.getJoinClause(toCMRField, toParentAlias,
1146: toChildAlias, buf);
1147: } else {
1148: buf.append(SQLUtil.SELECT);
1149: SQLUtil.getColumnNamesClause(
1150: toCMRField.getRelatedCMRField()
1151: .getTableKeyFields(),
1152: relationTableAlias, buf).append(SQLUtil.FROM)
1153: .append(toCMRField.getQualifiedTableName())
1154: .append(' ').append(relationTableAlias).append(
1155: SQLUtil.WHERE);
1156: SQLUtil.getRelationTableJoinClause(toCMRField,
1157: toParentAlias, relationTableAlias, buf);
1158: }
1159: }
1160:
1161: buf.append(SQLUtil.AND);
1162:
1163: // second part makes fromNode equal toChild
1164: if (fromAlias != null) {
1165: // compre pk to pk
1166: if (relationTableAlias == null) {
1167: SQLUtil.getSelfCompareWhereClause(toChildEntity
1168: .getPrimaryKeyFields(), toChildAlias,
1169: fromAlias, buf);
1170: } else {
1171: SQLUtil.getRelationTableJoinClause(toCMRField
1172: .getRelatedCMRField(), fromAlias,
1173: relationTableAlias, buf);
1174: }
1175: } else {
1176: // add the parameters
1177: inputParameters.addAll(QueryParameter.createParameters(
1178: fromParamNumber - 1, toChildEntity));
1179:
1180: // compare pk to parameter
1181: if (relationTableAlias == null) {
1182: SQLUtil.getWhereClause(toChildEntity
1183: .getPrimaryKeyFields(), toChildAlias, buf);
1184: } else {
1185: SQLUtil.getWhereClause(toCMRField.getRelatedCMRField()
1186: .getTableKeyFields(), relationTableAlias, buf);
1187: }
1188: }
1189:
1190: buf.append(')');
1191:
1192: return buf;
1193: }
1194:
1195: public Object visit(ASTValueClassComparison node, Object data) {
1196: StringBuffer buf = (StringBuffer) data;
1197:
1198: boolean not = (node.opp.equals(SQLUtil.NOT_EQUAL));
1199: String comparison = node.opp;
1200: buf.append('(');
1201: if (not) {
1202: buf.append(SQLUtil.NOT).append('(');
1203: comparison = "=";
1204: }
1205:
1206: // setup the from path
1207: ASTPath fromPath = (ASTPath) node.jjtGetChild(0);
1208: addJoinPath(fromPath);
1209: String fromAlias = aliasManager.getAlias(fromPath
1210: .getPath(fromPath.size() - 2));
1211: JDBCCMPFieldBridge fromCMPField = (JDBCCMPFieldBridge) fromPath
1212: .getCMPField();
1213:
1214: Node toNode = node.jjtGetChild(1);
1215: if (toNode instanceof ASTParameter) {
1216: ASTParameter toParam = (ASTParameter) toNode;
1217:
1218: // can only compare like kind entities
1219: Class parameterType = getParameterType(toParam.number);
1220: if (!(fromCMPField.getFieldType().equals(parameterType))) {
1221: throw new IllegalStateException(
1222: "Only like types can be "
1223: + "compared: from CMP field="
1224: + fromCMPField.getFieldType()
1225: + " to parameter=" + parameterType);
1226: }
1227:
1228: inputParameters.addAll(QueryParameter.createParameters(
1229: toParam.number - 1, fromCMPField));
1230: SQLUtil.getWhereClause(fromCMPField.getJDBCType(),
1231: fromAlias, comparison, buf);
1232: } else {
1233: ASTPath toPath = (ASTPath) toNode;
1234: addJoinPath(toPath);
1235: String toAlias = aliasManager.getAlias(toPath
1236: .getPath(toPath.size() - 2));
1237: JDBCCMPFieldBridge toCMPField = (JDBCCMPFieldBridge) toPath
1238: .getCMPField();
1239:
1240: // can only compare like kind entities
1241: if (!(fromCMPField.getFieldType().equals(toCMPField
1242: .getFieldType()))) {
1243: throw new IllegalStateException(
1244: "Only like types can be "
1245: + "compared: from CMP field="
1246: + fromCMPField.getFieldType()
1247: + " to CMP field="
1248: + toCMPField.getFieldType());
1249: }
1250:
1251: SQLUtil.getSelfCompareWhereClause(fromCMPField, toCMPField,
1252: fromAlias, toAlias, comparison, buf);
1253: }
1254:
1255: return (not ? buf.append(')') : buf).append(')');
1256: }
1257:
1258: /**
1259: * compreEntity(arg0, arg1)
1260: */
1261: public Object visit(ASTEntityComparison node, Object data) {
1262: StringBuffer buf = (StringBuffer) data;
1263: Node arg0 = node.jjtGetChild(0);
1264: Node arg1 = node.jjtGetChild(1);
1265: if (node.opp.equals(SQLUtil.NOT_EQUAL)) {
1266: compareEntity(true, arg0, arg1, buf);
1267: } else {
1268: compareEntity(false, arg0, arg1, buf);
1269: }
1270: return buf;
1271: }
1272:
1273: /**
1274: * Type-mapping function translation
1275: */
1276: public Object visit(ASTConcat node, Object data) {
1277: StringBuffer buf = (StringBuffer) data;
1278: JDBCFunctionMappingMetaData function = typeMapping
1279: .getFunctionMapping(JDBCTypeMappingMetaData.CONCAT);
1280: Object[] args = new Object[] {
1281: new NodeStringWrapper(node.jjtGetChild(0)),
1282: new NodeStringWrapper(node.jjtGetChild(1)), };
1283: function.getFunctionSql(args, buf);
1284: return buf;
1285: }
1286:
1287: /**
1288: * Type-mapping function translation
1289: */
1290: public Object visit(ASTSubstring node, Object data) {
1291: StringBuffer buf = (StringBuffer) data;
1292: JDBCFunctionMappingMetaData function = typeMapping
1293: .getFunctionMapping(JDBCTypeMappingMetaData.SUBSTRING);
1294: Object[] args = new Object[] {
1295: new NodeStringWrapper(node.jjtGetChild(0)),
1296: new NodeStringWrapper(node.jjtGetChild(1)),
1297: new NodeStringWrapper(node.jjtGetChild(2)), };
1298: function.getFunctionSql(args, buf);
1299: return buf;
1300: }
1301:
1302: /**
1303: * Type-mapping function translation
1304: */
1305: public Object visit(ASTLCase node, Object data) {
1306: StringBuffer buf = (StringBuffer) data;
1307: JDBCFunctionMappingMetaData function = typeMapping
1308: .getFunctionMapping(JDBCTypeMappingMetaData.LCASE);
1309: Object[] args = new Object[] { new NodeStringWrapper(node
1310: .jjtGetChild(0)), };
1311: function.getFunctionSql(args, buf);
1312: return buf;
1313: }
1314:
1315: /**
1316: * Type-mapping function translation
1317: */
1318: public Object visit(ASTUCase node, Object data) {
1319: StringBuffer buf = (StringBuffer) data;
1320: JDBCFunctionMappingMetaData function = typeMapping
1321: .getFunctionMapping(JDBCTypeMappingMetaData.UCASE);
1322: Object[] args = new Object[] { new NodeStringWrapper(node
1323: .jjtGetChild(0)), };
1324: function.getFunctionSql(args, buf);
1325: return buf;
1326: }
1327:
1328: /**
1329: * Type-mapping function translation
1330: */
1331: public Object visit(ASTLength node, Object data) {
1332: StringBuffer buf = (StringBuffer) data;
1333: JDBCFunctionMappingMetaData function = typeMapping
1334: .getFunctionMapping(JDBCTypeMappingMetaData.LENGTH);
1335: Object[] args = new Object[] { new NodeStringWrapper(node
1336: .jjtGetChild(0)), };
1337: function.getFunctionSql(args, buf);
1338: return buf;
1339: }
1340:
1341: /**
1342: * Type-mapping function translation
1343: */
1344: public Object visit(ASTLocate node, Object data) {
1345: StringBuffer buf = (StringBuffer) data;
1346:
1347: JDBCFunctionMappingMetaData function = typeMapping
1348: .getFunctionMapping(JDBCTypeMappingMetaData.LOCATE);
1349: Object[] args = new Object[3];
1350: args[0] = new NodeStringWrapper(node.jjtGetChild(0));
1351: args[1] = new NodeStringWrapper(node.jjtGetChild(1));
1352: if (node.jjtGetNumChildren() == 3) {
1353: args[2] = new NodeStringWrapper(node.jjtGetChild(2));
1354: } else {
1355: args[2] = "1";
1356: }
1357:
1358: // add the sql to the current buffer
1359: function.getFunctionSql(args, buf);
1360: return buf;
1361: }
1362:
1363: /**
1364: * Type-mapping function translation
1365: */
1366: public Object visit(ASTAbs node, Object data) {
1367: StringBuffer buf = (StringBuffer) data;
1368: JDBCFunctionMappingMetaData function = typeMapping
1369: .getFunctionMapping(JDBCTypeMappingMetaData.ABS);
1370: Object[] args = new Object[] { new NodeStringWrapper(node
1371: .jjtGetChild(0)), };
1372: function.getFunctionSql(args, buf);
1373: return buf;
1374: }
1375:
1376: /**
1377: * Type-mapping function translation
1378: */
1379: public Object visit(ASTMod node, Object data) {
1380: StringBuffer buf = (StringBuffer) data;
1381: JDBCFunctionMappingMetaData function = typeMapping
1382: .getFunctionMapping(JDBCTypeMappingMetaData.MOD);
1383: Object[] args = new Object[] {
1384: new NodeStringWrapper(node.jjtGetChild(0)),
1385: new NodeStringWrapper(node.jjtGetChild(1)), };
1386: function.getFunctionSql(args, buf);
1387: return buf;
1388: }
1389:
1390: /**
1391: * Type-mapping function translation
1392: */
1393: public Object visit(ASTSqrt node, Object data) {
1394: StringBuffer buf = (StringBuffer) data;
1395: JDBCFunctionMappingMetaData function = typeMapping
1396: .getFunctionMapping(JDBCTypeMappingMetaData.SQRT);
1397: Object[] args = new Object[] { new NodeStringWrapper(node
1398: .jjtGetChild(0)) };
1399: function.getFunctionSql(args, buf);
1400: return buf;
1401: }
1402:
1403: public Object visit(ASTCount node, Object data) {
1404: StringBuffer buf = (StringBuffer) data;
1405: node.setResultType(returnType);
1406:
1407: Object args[];
1408: final ASTPath cntPath = (ASTPath) node.jjtGetChild(0);
1409: if (cntPath.isCMPField()) {
1410: args = new Object[] { node.distinct,
1411: new NodeStringWrapper(cntPath) };
1412: } else {
1413: JDBCEntityBridge entity = (JDBCEntityBridge) cntPath
1414: .getEntity();
1415: final JDBCFieldBridge[] pkFields = entity
1416: .getPrimaryKeyFields();
1417: if (pkFields.length > 1) {
1418: countCompositePk = true;
1419: forceDistinct = node.distinct.length() > 0;
1420: selectEntity(cntPath, buf);
1421: return buf;
1422: } else {
1423:
1424: final String alias = aliasManager.getAlias(cntPath
1425: .getPath());
1426: StringBuffer keyColumn = new StringBuffer(20);
1427: SQLUtil.getColumnNamesClause(pkFields[0], alias,
1428: keyColumn);
1429: args = new Object[] { node.distinct,
1430: keyColumn.toString() };
1431: }
1432: }
1433:
1434: return JDBCTypeMappingMetaData.COUNT_FUNC.getFunctionSql(args,
1435: buf);
1436: }
1437:
1438: public Object visit(ASTMax node, Object data) {
1439: node.setResultType(returnType);
1440: StringBuffer buf = (StringBuffer) data;
1441: Object[] args = new Object[] { node.distinct,
1442: new NodeStringWrapper(node.jjtGetChild(0)) };
1443: return JDBCTypeMappingMetaData.MAX_FUNC.getFunctionSql(args,
1444: buf);
1445: }
1446:
1447: public Object visit(ASTMin node, Object data) {
1448: node.setResultType(returnType);
1449: StringBuffer buf = (StringBuffer) data;
1450: Object[] args = new Object[] { node.distinct,
1451: new NodeStringWrapper(node.jjtGetChild(0)) };
1452: return JDBCTypeMappingMetaData.MIN_FUNC.getFunctionSql(args,
1453: buf);
1454: }
1455:
1456: public Object visit(ASTAvg node, Object data) {
1457: node.setResultType(returnType);
1458: StringBuffer buf = (StringBuffer) data;
1459: Object[] args = new Object[] { node.distinct,
1460: new NodeStringWrapper(node.jjtGetChild(0)), };
1461: return JDBCTypeMappingMetaData.AVG_FUNC.getFunctionSql(args,
1462: buf);
1463: }
1464:
1465: public Object visit(ASTSum node, Object data) {
1466: node.setResultType(returnType);
1467: StringBuffer buf = (StringBuffer) data;
1468: Object[] args = new Object[] { node.distinct,
1469: new NodeStringWrapper(node.jjtGetChild(0)) };
1470: return JDBCTypeMappingMetaData.SUM_FUNC.getFunctionSql(args,
1471: buf);
1472: }
1473:
1474: /**
1475: * tableAlias.columnName
1476: */
1477: public Object visit(ASTPath node, Object data) {
1478: StringBuffer buf = (StringBuffer) data;
1479: if (!node.isCMPField()) {
1480: throw new IllegalStateException(
1481: "Can only visit cmp valued path "
1482: + "node. Should have been handled at a higher level.");
1483: }
1484:
1485: JDBCCMPFieldBridge cmpField = (JDBCCMPFieldBridge) node
1486: .getCMPField();
1487:
1488: // make sure this is mapped to a single column
1489: switch (node.type) {
1490: case EJBQLTypes.ENTITY_TYPE:
1491: case EJBQLTypes.VALUE_CLASS_TYPE:
1492: if (cmpField.getJDBCType().hasMapper()) {
1493: break;
1494: }
1495: case EJBQLTypes.UNKNOWN_TYPE:
1496: throw new IllegalStateException(
1497: "Can not visit multi-column path "
1498: + "node. Should have been handled at a higher level.");
1499: }
1500:
1501: addJoinPath(node);
1502: String alias = aliasManager.getAlias(node
1503: .getPath(node.size() - 2));
1504: SQLUtil.getColumnNamesClause(cmpField, alias, buf);
1505: return buf;
1506: }
1507:
1508: public Object visit(ASTAbstractSchema node, Object data) {
1509: throw new IllegalStateException(
1510: "Can not visit abstract schema node. "
1511: + "Should have been handled at a higher level.");
1512: }
1513:
1514: /**
1515: * ?
1516: */
1517: public Object visit(ASTParameter node, Object data) {
1518: StringBuffer buf = (StringBuffer) data;
1519: Class type = getParameterType(node.number);
1520:
1521: // make sure this is mapped to a single column
1522: int ejbqlType = EJBQLTypes.getEJBQLType(type);
1523: if (ejbqlType == EJBQLTypes.ENTITY_TYPE
1524: || ejbqlType == EJBQLTypes.VALUE_CLASS_TYPE
1525: || ejbqlType == EJBQLTypes.UNKNOWN_TYPE) {
1526: throw new IllegalStateException(
1527: "Can not visit multi-column "
1528: + "parameter node. Should have been handled at a higher level.");
1529: }
1530:
1531: QueryParameter param = new QueryParameter(node.number - 1,
1532: typeFactory.getJDBCType(type));
1533: inputParameters.add(param);
1534: buf.append('?');
1535: return buf;
1536: }
1537:
1538: /**
1539: * typeMapping.get<True/False>Mapping()
1540: */
1541: public Object visit(ASTBooleanLiteral node, Object data) {
1542: StringBuffer buf = (StringBuffer) data;
1543: if (node.value) {
1544: buf.append(typeMapping.getTrueMapping());
1545: } else {
1546: buf.append(typeMapping.getFalseMapping());
1547: }
1548: return data;
1549: }
1550:
1551: public Object visit(ASTLimitOffset node, Object data) {
1552: int child = 0;
1553: if (node.hasOffset) {
1554: Node offsetNode = node.jjtGetChild(child++);
1555: if (offsetNode instanceof ASTParameter) {
1556: ASTParameter param = (ASTParameter) offsetNode;
1557: Class parameterType = getParameterType(param.number);
1558: if (int.class != parameterType
1559: && Integer.class != parameterType) {
1560: throw new UnsupportedOperationException(
1561: "OFFSET parameter must be an int");
1562: }
1563: offsetParam = param.number;
1564: } else {
1565: ASTExactNumericLiteral param = (ASTExactNumericLiteral) offsetNode;
1566: offsetValue = (int) param.value;
1567: }
1568: }
1569: if (node.hasLimit) {
1570: Node limitNode = node.jjtGetChild(child);
1571: if (limitNode instanceof ASTParameter) {
1572: ASTParameter param = (ASTParameter) limitNode;
1573: Class parameterType = getParameterType(param.number);
1574: if (int.class != parameterType
1575: && Integer.class != parameterType) {
1576: throw new UnsupportedOperationException(
1577: "LIMIT parameter must be an int");
1578: }
1579: limitParam = param.number;
1580: } else {
1581: ASTExactNumericLiteral param = (ASTExactNumericLiteral) limitNode;
1582: limitValue = (int) param.value;
1583: }
1584: }
1585: return data;
1586: }
1587:
1588: public Object visit(ASTWhereConditionalTerm node, Object data) {
1589: // clear per term paths
1590: clearPerTermJoinPaths();
1591:
1592: StringBuffer buf = (StringBuffer) data;
1593: buf.append('(');
1594: for (int i = 0; i < node.jjtGetNumChildren(); ++i) {
1595: node.jjtGetChild(i).jjtAccept(this , data);
1596: }
1597:
1598: StringBuffer thetaJoin = new StringBuffer();
1599: createThetaJoin(thetaJoin);
1600:
1601: if (thetaJoin.length() > 0) {
1602: buf.append(SQLUtil.AND).append(thetaJoin.toString());
1603: }
1604:
1605: buf.append(')');
1606: return data;
1607: }
1608:
1609: /**
1610: * Wrap a node with a class that when ever toString is called visits the
1611: * node. This is used by the function implmentations, for parameters.
1612: * <p/>
1613: * Be careful with this class because it visits the node for each call of
1614: * toString, which could have undesireable result if called multiple times.
1615: */
1616: private final class NodeStringWrapper {
1617: final Node node;
1618:
1619: public NodeStringWrapper(Node node) {
1620: this .node = node;
1621: }
1622:
1623: public String toString() {
1624: return node.jjtAccept(JDBCEJBQLCompiler.this ,
1625: new StringBuffer()).toString();
1626: }
1627: }
1628:
1629: /**
1630: * Recursively searches for ASTPath among children.
1631: *
1632: * @param selectFunction a node implements SelectFunction
1633: * @return ASTPath child or null if there was no child of type ASTPath
1634: */
1635: private ASTPath getPathFromChildren(Node selectFunction) {
1636: for (int childInd = 0; childInd < selectFunction
1637: .jjtGetNumChildren(); ++childInd) {
1638: Node child = selectFunction.jjtGetChild(childInd);
1639: if (child instanceof ASTPath) {
1640: return (ASTPath) child;
1641: } else if (child instanceof SelectFunction) {
1642: Node path = getPathFromChildren(child);
1643: if (path != null) {
1644: return (ASTPath) path;
1645: }
1646: }
1647: }
1648: return null;
1649: }
1650:
1651: /**
1652: * Checks whether the path passed in is already in the SELECT clause.
1653: *
1654: * @param path the path to check.
1655: * @return true if the path is already in the SELECT clause.
1656: */
1657: private boolean isSelected(ASTPath path) {
1658: boolean selected = false;
1659:
1660: CMPFieldBridge cmpField = path.getCMPField();
1661: if (selectObject instanceof JDBCCMPFieldBridge
1662: && cmpField == selectObject) {
1663: selected = true;
1664: } else if (selectObject instanceof JDBCEntityBridge) {
1665: JDBCEntityBridge entity = (JDBCEntityBridge) selectObject;
1666: JDBCFieldBridge[] pkFields = entity.getPrimaryKeyFields();
1667: for (int pkInd = 0; pkInd < pkFields.length; ++pkInd) {
1668: if (pkFields[pkInd] == cmpField) {
1669: selected = true;
1670: break;
1671: }
1672: }
1673: } else if (selectObject instanceof SelectFunction) {
1674: Node funcNode = (Node) selectObject;
1675: ASTPath fieldPath = getPathFromChildren(funcNode);
1676: if (fieldPath.getCMPField() == cmpField) {
1677: selected = true;
1678: }
1679: }
1680:
1681: return selected;
1682: }
1683:
1684: private void selectEntity(ASTPath path, StringBuffer buf) {
1685: JDBCEntityBridge selectEntity = (JDBCEntityBridge) path
1686: .getEntity();
1687:
1688: StringBuffer columnNamesClause = new StringBuffer(200);
1689: addJoinPath(path);
1690: selectAlias = aliasManager.getAlias(path.getPath());
1691:
1692: // get a list of all fields to be loaded
1693: // get the identifier for this field
1694: SQLUtil.getColumnNamesClause(
1695: selectEntity.getPrimaryKeyFields(), selectAlias,
1696: columnNamesClause);
1697:
1698: if (readAhead.isOnFind()) {
1699: String eagerLoadGroupName = readAhead.getEagerLoadGroup();
1700: boolean[] loadGroupMask = selectEntity
1701: .getLoadGroupMask(eagerLoadGroupName);
1702: SQLUtil.appendColumnNamesClause(selectEntity
1703: .getTableFields(), loadGroupMask, selectAlias,
1704: columnNamesClause);
1705:
1706: try {
1707: leftJoinCMRList = JDBCAbstractQueryCommand
1708: .getLeftJoinCMRNodes(selectEntity, path
1709: .getPath(), readAhead.getLeftJoins(),
1710: declaredPaths);
1711: } catch (DeploymentException e) {
1712: throw new IllegalStateException(e.getMessage());
1713: }
1714:
1715: if (!leftJoinCMRList.isEmpty()) {
1716: onFindCMRJoin = new StringBuffer(100);
1717: JDBCAbstractQueryCommand.leftJoinCMRNodes(selectAlias,
1718: leftJoinCMRList, aliasManager, onFindCMRJoin);
1719: JDBCAbstractQueryCommand.appendLeftJoinCMRColumnNames(
1720: leftJoinCMRList, aliasManager,
1721: columnNamesClause);
1722: }
1723: }
1724: buf.append(columnNamesClause);
1725: }
1726:
1727: private void addJoinPath(ASTPath path) {
1728: ctermJoinPaths.add(path);
1729: allJoinPaths.add(path);
1730: }
1731:
1732: private void addCollectionMemberJoinPath(String alias, ASTPath path) {
1733: ctermCollectionMemberJoinPaths.put(alias, path);
1734: allCollectionMemberJoinPaths.put(alias, path);
1735: }
1736:
1737: private void addLeftJoinPath(String pathStr, ASTPath path) {
1738: Set set = (Set) ctermLeftJoinPaths.get(pathStr);
1739: if (set == null) {
1740: set = new HashSet();
1741: ctermLeftJoinPaths.put(pathStr, set);
1742: }
1743: set.add(path);
1744:
1745: set = (Set) allLeftJoinPaths.get(pathStr);
1746: if (set == null) {
1747: set = new HashSet();
1748: allLeftJoinPaths.put(pathStr, set);
1749: }
1750: set.add(path);
1751: }
1752:
1753: private void clearPerTermJoinPaths() {
1754: ctermJoinPaths.clear();
1755: ctermCollectionMemberJoinPaths.clear();
1756: ctermLeftJoinPaths.clear();
1757: }
1758:
1759: private boolean isDistinct(Node selectNode) {
1760: return ((ASTSelect) selectNode).distinct
1761: || returnType.equals(Set.class) || forceDistinct;
1762: }
1763: }
|