From: <me...@us...> - 2002-10-01 02:07:43
|
Update of /cvsroot/cayenne/cayenne/src/cayenne/java/org/objectstyle/cayenne/access/trans In directory usw-pr-cvs1:/tmp/cvs-serv8640/src/cayenne/java/org/objectstyle/cayenne/access/trans Modified Files: QueryAssembler.java QueryAssemblerHelper.java SelectTranslator.java Log Message: applied patch by Craig Miskell Index: QueryAssembler.java =================================================================== RCS file: /cvsroot/cayenne/cayenne/src/cayenne/java/org/objectstyle/cayenne/access/trans/QueryAssembler.java,v retrieving revision 1.1 retrieving revision 1.2 diff -C2 -d -r1.1 -r1.2 *** QueryAssembler.java 8 Sep 2002 22:46:47 -0000 1.1 --- QueryAssembler.java 1 Oct 2002 02:07:39 -0000 1.2 *************** *** 97,100 **** --- 97,104 ---- public abstract String createSqlString() throws java.lang.Exception; + public String aliasForTable(DbEntity ent, DbRelationship rel) { + return aliasForTable(ent); //Default implementation + } + /** * Returns a name that can be used as column alias. *************** *** 128,132 **** /** Translates internal query into PreparedStatement. */ public PreparedStatement createStatement(Level logLevel) throws Exception { ! long t1 = System.currentTimeMillis(); String sqlStr = createSqlString(); QueryLogger.logQuery(logLevel, sqlStr, values, System.currentTimeMillis() - t1); --- 132,136 ---- /** Translates internal query into PreparedStatement. */ public PreparedStatement createStatement(Level logLevel) throws Exception { ! long t1 = System.currentTimeMillis(); String sqlStr = createSqlString(); QueryLogger.logQuery(logLevel, sqlStr, values, System.currentTimeMillis() - t1); *************** *** 155,160 **** if (attr == null) { stmt.setObject(i + 1, val); ! } ! else { int type = attr.getType(); int precision = attr.getPrecision(); --- 159,163 ---- if (attr == null) { stmt.setObject(i + 1, val); ! } else { int type = attr.getType(); int precision = attr.getPrecision(); *************** *** 164,169 **** else { ExtendedType map = ! adapter.getTypeConverter().getRegisteredType(val.getClass().getName()); ! Object jdbcVal = (map == null) ? val : map.toJdbcObject(val, type); stmt.setObject(i + 1, jdbcVal, type, precision); } --- 167,174 ---- else { ExtendedType map = ! adapter.getTypeConverter().getRegisteredType( ! val.getClass().getName()); ! Object jdbcVal = ! (map == null) ? val : map.toJdbcObject(val, type); stmt.setObject(i + 1, jdbcVal, type, precision); } Index: QueryAssemblerHelper.java =================================================================== RCS file: /cvsroot/cayenne/cayenne/src/cayenne/java/org/objectstyle/cayenne/access/trans/QueryAssemblerHelper.java,v retrieving revision 1.1 retrieving revision 1.2 diff -C2 -d -r1.1 -r1.2 *** QueryAssemblerHelper.java 8 Sep 2002 22:46:47 -0000 1.1 --- QueryAssemblerHelper.java 1 Oct 2002 02:07:39 -0000 1.2 *************** *** 70,391 **** */ public abstract class QueryAssemblerHelper { ! static Logger logObj = Logger.getLogger(QueryAssemblerHelper.class.getName()); ! protected QueryAssembler queryAssembler; ! public QueryAssemblerHelper() {} ! /** Creates QueryAssemblerHelper. Sets queryAssembler property. */ ! public QueryAssemblerHelper(QueryAssembler queryAssembler) { ! this.queryAssembler = queryAssembler; ! } ! /** Returns parent QueryAssembler that uses this helper. */ ! public QueryAssembler getQueryAssembler() { ! return queryAssembler; ! } ! public void setQueryAssembler(QueryAssembler queryAssembler) { ! this.queryAssembler = queryAssembler; ! } ! /** Translates the part of parent translator's query that is supported ! * by this PartTranslator. For example, QualifierTranslator will process ! * qualifier expression, OrderingTranslator - ordering of the query. ! * In the process of translation parent translator is notified of any ! * join tables added (so that it can update its "FROM" clause). ! * Also parent translator is consulted about table aliases to use ! * when translating columns. */ ! public abstract String doTranslation(); ! public ObjEntity getObjEntity() { ! return getQueryAssembler().getRootEntity(); ! } ! public DbEntity getDbEntity() { ! return getQueryAssembler().getRootEntity().getDbEntity(); ! } ! /** Processes parts of the OBJ_PATH expression. */ ! protected void appendObjPath(StringBuffer buf, Expression pathExp) { ! Iterator it = getObjEntity().resolvePathComponents(pathExp); ! while (it.hasNext()) { ! Object pathComp = it.next(); ! if (pathComp instanceof ObjRelationship) { ! ObjRelationship rel = (ObjRelationship) pathComp; ! // if this is a last relationship in the path, ! // it needs special handling ! if (!it.hasNext()) { ! processRelTermination(buf, rel); ! } else { ! // find and add joins .... ! processRelParts(rel); ! } ! } else { ! ObjAttribute objAttr = (ObjAttribute) pathComp; ! processColumn(buf, objAttr.getDbAttribute()); ! } ! } ! } ! protected void appendDbPath(StringBuffer buf, Expression pathExp) { ! Iterator it = getDbEntity().resolvePathComponents(pathExp); ! while (it.hasNext()) { ! Object pathComp = it.next(); ! if (pathComp instanceof DbRelationship) { ! DbRelationship rel = (DbRelationship) pathComp; ! // if this is a last relationship in the path, ! // it needs special handling ! if (!it.hasNext()) { ! processRelTermination(buf, rel); ! } else { ! // find and add joins .... ! queryAssembler.dbRelationshipAdded(rel); ! } ! } else { ! DbAttribute dbAttr = (DbAttribute) pathComp; ! processColumn(buf, dbAttr); ! } ! } ! } ! /** Appends column name of a column in a root entity. */ ! protected void processColumn(StringBuffer buf, Expression nameExp) { ! if (queryAssembler.supportsTableAliases()) { ! String alias = queryAssembler.aliasForTable(getDbEntity()); ! buf.append(alias).append('.'); ! } ! buf.append(nameExp.getOperand(0)); ! } ! protected void processColumn(StringBuffer buf, DbAttribute dbAttr) { ! String alias = ! (queryAssembler.supportsTableAliases()) ! ? queryAssembler.aliasForTable((DbEntity) dbAttr.getEntity()) ! : null; ! buf.append(dbAttr.getAliasedName(alias)); ! } ! /** ! * Appends SQL code to the query buffer to handle <code>val</code> as a ! * parameter to the PreparedStatement being built. Adds <code>val</code> ! * into QueryAssembler parameter list. ! * ! * <p>If <code>val</code> is null, "NULL" is appended to the query. </p> ! * ! * <p>If <code>val</code> is a DataObject, its primary key value is ! * used as a parameter. <i>Only objects with a single column primary key ! * can be used.</i> ! * ! * @param buf query buffer. ! * ! * @param val object that should be appended as a literal to the query. ! * Must be of one of "standard JDBC" types, null or a DataObject. ! * ! * @param attr DbAttribute that has information on what type of parameter ! * is being appended. ! * ! */ ! protected void appendLiteral(StringBuffer buf, Object val, DbAttribute attr) { ! if (val == null) { ! buf.append("NULL"); ! } else if (val instanceof DataObject) { ! ObjectId id = ((DataObject) val).getObjectId(); ! // check if this id is acceptable to be a parameter ! if (id == null) { ! throw new CayenneRuntimeException("Can't use TRANSIENT object as a query parameter."); ! } ! if (id.isTemporary()) { ! throw new CayenneRuntimeException("Can't use NEW object as a query parameter."); ! } ! Map snap = id.getIdSnapshot(); ! if (snap.size() != 1) { ! StringBuffer msg = new StringBuffer(); ! msg ! .append("Object must have a single primary key column ") ! .append("to serve as a query parameter. ") ! .append("This object has ") ! .append(snap.size()) ! .append(": ") ! .append(snap); ! throw new CayenneRuntimeException(msg.toString()); ! } ! // checks have been passed, use id value ! appendLiteralDirect(buf, snap.get(snap.keySet().iterator().next()), attr); ! } else { ! appendLiteralDirect(buf, val, attr); ! } ! } ! /** ! * Appends SQL code to the query buffer to handle <code>val</code> as a ! * parameter to the PreparedStatement being built. Adds <code>val</code> ! * into QueryAssembler parameter list. ! * ! * ! * @param buf query buffer ! * @param val object that should be appended as a literal to the query. ! * Must be of one of "standard JDBC" types. Can not be null. ! */ ! private final void appendLiteralDirect( ! StringBuffer buf, ! Object val, ! DbAttribute attr) { ! buf.append('?'); ! // we are hoping that when processing parameter list, ! // the correct type will be ! // guessed without looking at DbAttribute... ! queryAssembler.addToParamList(attr, val); ! } ! /** ! * Returns database type of expression parameters or ! * null if it can not be determined. ! */ ! protected DbAttribute paramsDbType(Expression e) { ! int len = e.getOperandCount(); ! // ignore unary expressions ! if (len < 2) { ! return null; ! } ! // naive algorithm: ! // if at least one of the sibling operands is a ! // OBJ_PATH expression, use its attribute type as ! // a final answer. ! for (int i = 0; i < len; i++) { ! Object op = e.getOperand(i); ! if (op instanceof Expression) { ! Expression ope = (Expression) op; ! if (ope.getType() == Expression.OBJ_PATH) { ! Iterator it = getObjEntity().resolvePathComponents(ope); ! while (it.hasNext()) { ! Object pathComp = it.next(); ! if (pathComp instanceof ObjAttribute) { ! return ((ObjAttribute) pathComp).getDbAttribute(); ! } ! } ! } ! } ! } ! return null; ! } ! /** ! * Processes ObjRelationship. Decomposes it into DbRelationships ! * and appends parts to the query buffer. ! */ ! protected void processRelParts(ObjRelationship rel) { ! Iterator it = rel.getDbRelationshipList().iterator(); ! while (it.hasNext()) { ! queryAssembler.dbRelationshipAdded((DbRelationship) it.next()); ! } ! } ! /** Processes case when an OBJ_PATH expression ends with relationship. ! * If this is a "to many" relationship, a join is added and a column ! * expression for the target entity primary key. If this is a "to one" ! * relationship, column expresion for the source foreign key is added. ! */ ! protected void processRelTermination(StringBuffer buf, ObjRelationship rel) { ! if (rel.isToMany()) { ! // append joins ! processRelParts(rel); ! } ! List dbRels = rel.getDbRelationshipList(); ! // get last DbRelationship on the list ! DbRelationship dbRel = (DbRelationship) dbRels.get(dbRels.size() - 1); ! List joins = dbRel.getJoins(); ! if (joins.size() != 1) { ! StringBuffer msg = new StringBuffer(); ! msg ! .append("OBJ_PATH expressions are only supported ") ! .append("for a single-join relationships. ") ! .append("This relationship has ") ! .append(joins.size()) ! .append(" joins."); ! throw new CayenneRuntimeException(msg.toString()); ! } ! DbAttributePair join = (DbAttributePair) joins.get(0); - DbAttribute att = join.getSource(); - processColumn(buf, att); - } - /** * Handles case when a DB_NAME expression ends with relationship. ! * If this is a "to many" relationship, a join is added and a column ! * expression for the target entity primary key. If this is a "to one" ! * relationship, column expresion for the source foreign key is added. ! */ ! protected void processRelTermination(StringBuffer buf, DbRelationship rel) { ! if (rel.isToMany()) { ! // append joins ! queryAssembler.dbRelationshipAdded(rel); ! } ! ! // get last DbRelationship on the list ! List joins = rel.getJoins(); ! if (joins.size() != 1) { ! StringBuffer msg = new StringBuffer(); ! msg ! .append("OBJ_PATH expressions are only supported ") ! .append("for a single-join relationships. ") ! .append("This relationship has ") ! .append(joins.size()) ! .append(" joins."); ! throw new CayenneRuntimeException(msg.toString()); ! } ! DbAttributePair join = (DbAttributePair) joins.get(0); ! DbAttribute att = null; ! if (rel.isToMany()) { ! DbEntity ent = (DbEntity) join.getTarget().getEntity(); ! List pk = ent.getPrimaryKey(); ! if (pk.size() != 1) { ! StringBuffer msg = new StringBuffer(); ! msg ! .append("DB_NAME expressions can only support ") ! .append("targets with a single column PK. ") ! .append("This entity has ") ! .append(pk.size()) ! .append(" columns in primary key."); ! throw new CayenneRuntimeException(msg.toString()); ! } ! att = (DbAttribute) pk.get(0); ! } else { ! att = join.getSource(); ! } ! processColumn(buf, att); ! } } --- 70,410 ---- */ public abstract class QueryAssemblerHelper { ! static Logger logObj = Logger.getLogger(QueryAssemblerHelper.class.getName()); ! protected QueryAssembler queryAssembler; ! public QueryAssemblerHelper() {} ! /** Creates QueryAssemblerHelper. Sets queryAssembler property. */ ! public QueryAssemblerHelper(QueryAssembler queryAssembler) { ! this.queryAssembler = queryAssembler; ! } ! /** Returns parent QueryAssembler that uses this helper. */ ! public QueryAssembler getQueryAssembler() { ! return queryAssembler; ! } ! public void setQueryAssembler(QueryAssembler queryAssembler) { ! this.queryAssembler = queryAssembler; ! } ! /** Translates the part of parent translator's query that is supported ! * by this PartTranslator. For example, QualifierTranslator will process ! * qualifier expression, OrderingTranslator - ordering of the query. ! * In the process of translation parent translator is notified of any ! * join tables added (so that it can update its "FROM" clause). ! * Also parent translator is consulted about table aliases to use ! * when translating columns. */ ! public abstract String doTranslation(); ! public ObjEntity getObjEntity() { ! return getQueryAssembler().getRootEntity(); ! } ! public DbEntity getDbEntity() { ! return getQueryAssembler().getRootEntity().getDbEntity(); ! } ! /** Processes parts of the OBJ_PATH expression. */ ! protected void appendObjPath(StringBuffer buf, Expression pathExp) { ! Iterator it = getObjEntity().resolvePathComponents(pathExp); ! ObjRelationship lastRelationship = null; ! while (it.hasNext()) { ! Object pathComp = it.next(); ! if (pathComp instanceof ObjRelationship) { ! ObjRelationship rel = (ObjRelationship) pathComp; ! // if this is a last relationship in the path, ! // it needs special handling ! if (!it.hasNext()) { ! processRelTermination(buf, rel); ! } else { ! // find and add joins .... ! processRelParts(rel); ! } ! lastRelationship = rel; ! } else { ! ObjAttribute objAttr = (ObjAttribute) pathComp; ! if (lastRelationship != null) { ! DbRelationship lastDbRel = ! (DbRelationship) lastRelationship.getDbRelationshipList().get(0); ! processColumn(buf, objAttr.getDbAttribute(), lastDbRel); ! } else { ! processColumn(buf, objAttr.getDbAttribute()); ! } ! } ! } ! } ! protected void appendDbPath(StringBuffer buf, Expression pathExp) { ! Iterator it = getDbEntity().resolvePathComponents(pathExp); ! while (it.hasNext()) { ! Object pathComp = it.next(); ! if (pathComp instanceof DbRelationship) { ! DbRelationship rel = (DbRelationship) pathComp; ! // if this is a last relationship in the path, ! // it needs special handling ! if (!it.hasNext()) { ! processRelTermination(buf, rel); ! } else { ! // find and add joins .... ! queryAssembler.dbRelationshipAdded(rel); ! } ! } else { ! DbAttribute dbAttr = (DbAttribute) pathComp; ! processColumn(buf, dbAttr); ! } ! } ! } ! /** Appends column name of a column in a root entity. */ ! protected void processColumn(StringBuffer buf, Expression nameExp) { ! if (queryAssembler.supportsTableAliases()) { ! String alias = queryAssembler.aliasForTable(getDbEntity()); ! buf.append(alias).append('.'); ! } ! buf.append(nameExp.getOperand(0)); ! } ! protected void processColumn( ! StringBuffer buf, ! DbAttribute dbAttr, ! DbRelationship rel) { ! String alias = ! (queryAssembler.supportsTableAliases()) ! ? queryAssembler.aliasForTable((DbEntity) dbAttr.getEntity(), rel) ! : null; ! buf.append(dbAttr.getAliasedName(alias)); ! } ! protected void processColumn(StringBuffer buf, DbAttribute dbAttr) { ! String alias = ! (queryAssembler.supportsTableAliases()) ! ? queryAssembler.aliasForTable((DbEntity) dbAttr.getEntity()) ! : null; ! buf.append(dbAttr.getAliasedName(alias)); ! } ! /** ! * Appends SQL code to the query buffer to handle <code>val</code> as a ! * parameter to the PreparedStatement being built. Adds <code>val</code> ! * into QueryAssembler parameter list. ! * ! * <p>If <code>val</code> is null, "NULL" is appended to the query. </p> ! * ! * <p>If <code>val</code> is a DataObject, its primary key value is ! * used as a parameter. <i>Only objects with a single column primary key ! * can be used.</i> ! * ! * @param buf query buffer. ! * ! * @param val object that should be appended as a literal to the query. ! * Must be of one of "standard JDBC" types, null or a DataObject. ! * ! * @param attr DbAttribute that has information on what type of parameter ! * is being appended. ! * ! */ ! protected void appendLiteral(StringBuffer buf, Object val, DbAttribute attr) { ! if (val == null) { ! buf.append("NULL"); ! } else if (val instanceof DataObject) { ! ObjectId id = ((DataObject) val).getObjectId(); ! // check if this id is acceptable to be a parameter ! if (id == null) { ! throw new CayenneRuntimeException("Can't use TRANSIENT object as a query parameter."); ! } ! if (id.isTemporary()) { ! throw new CayenneRuntimeException("Can't use NEW object as a query parameter."); ! } ! Map snap = id.getIdSnapshot(); ! if (snap.size() != 1) { ! StringBuffer msg = new StringBuffer(); ! msg ! .append("Object must have a single primary key column ") ! .append("to serve as a query parameter. ") ! .append("This object has ") ! .append(snap.size()) ! .append(": ") ! .append(snap); ! throw new CayenneRuntimeException(msg.toString()); ! } ! // checks have been passed, use id value ! appendLiteralDirect(buf, snap.get(snap.keySet().iterator().next()), attr); ! } else { ! appendLiteralDirect(buf, val, attr); ! } ! } ! /** ! * Appends SQL code to the query buffer to handle <code>val</code> as a ! * parameter to the PreparedStatement being built. Adds <code>val</code> ! * into QueryAssembler parameter list. ! * ! * ! * @param buf query buffer ! * @param val object that should be appended as a literal to the query. ! * Must be of one of "standard JDBC" types. Can not be null. ! */ ! private final void appendLiteralDirect( ! StringBuffer buf, ! Object val, ! DbAttribute attr) { ! buf.append('?'); ! // we are hoping that when processing parameter list, ! // the correct type will be ! // guessed without looking at DbAttribute... ! queryAssembler.addToParamList(attr, val); ! } ! /** ! * Returns database type of expression parameters or ! * null if it can not be determined. ! */ ! protected DbAttribute paramsDbType(Expression e) { ! int len = e.getOperandCount(); ! // ignore unary expressions ! if (len < 2) { ! return null; ! } ! // naive algorithm: ! // if at least one of the sibling operands is a ! // OBJ_PATH expression, use its attribute type as ! // a final answer. ! for (int i = 0; i < len; i++) { ! Object op = e.getOperand(i); ! if (op instanceof Expression) { ! Expression ope = (Expression) op; ! if (ope.getType() == Expression.OBJ_PATH) { ! Iterator it = getObjEntity().resolvePathComponents(ope); ! while (it.hasNext()) { ! Object pathComp = it.next(); ! if (pathComp instanceof ObjAttribute) { ! return ((ObjAttribute) pathComp).getDbAttribute(); ! } ! } ! } ! } ! } ! return null; ! } ! /** ! * Processes ObjRelationship. Decomposes it into DbRelationships ! * and appends parts to the query buffer. ! */ ! protected void processRelParts(ObjRelationship rel) { ! Iterator it = rel.getDbRelationshipList().iterator(); ! while (it.hasNext()) { ! queryAssembler.dbRelationshipAdded((DbRelationship) it.next()); ! } ! } ! /** Processes case when an OBJ_PATH expression ends with relationship. ! * If this is a "to many" relationship, a join is added and a column ! * expression for the target entity primary key. If this is a "to one" ! * relationship, column expresion for the source foreign key is added. ! */ ! protected void processRelTermination(StringBuffer buf, ObjRelationship rel) { ! if (rel.isToMany()) { ! // append joins ! processRelParts(rel); ! } ! List dbRels = rel.getDbRelationshipList(); ! // get last DbRelationship on the list ! DbRelationship dbRel = (DbRelationship) dbRels.get(dbRels.size() - 1); ! List joins = dbRel.getJoins(); ! if (joins.size() != 1) { ! StringBuffer msg = new StringBuffer(); ! msg ! .append("OBJ_PATH expressions are only supported ") ! .append("for a single-join relationships. ") ! .append("This relationship has ") ! .append(joins.size()) ! .append(" joins."); ! ! throw new CayenneRuntimeException(msg.toString()); ! } ! ! DbAttributePair join = (DbAttributePair) joins.get(0); ! ! DbAttribute att = join.getSource(); ! processColumn(buf, att); ! } /** * Handles case when a DB_NAME expression ends with relationship. ! * If this is a "to many" relationship, a join is added and a column ! * expression for the target entity primary key. If this is a "to one" ! * relationship, column expresion for the source foreign key is added. ! */ ! protected void processRelTermination(StringBuffer buf, DbRelationship rel) { ! if (rel.isToMany()) { ! // append joins ! queryAssembler.dbRelationshipAdded(rel); ! } ! // get last DbRelationship on the list ! List joins = rel.getJoins(); ! if (joins.size() != 1) { ! StringBuffer msg = new StringBuffer(); ! msg ! .append("OBJ_PATH expressions are only supported ") ! .append("for a single-join relationships. ") ! .append("This relationship has ") ! .append(joins.size()) ! .append(" joins."); ! throw new CayenneRuntimeException(msg.toString()); ! } ! DbAttributePair join = (DbAttributePair) joins.get(0); ! DbAttribute att = null; ! if (rel.isToMany()) { ! DbEntity ent = (DbEntity) join.getTarget().getEntity(); ! List pk = ent.getPrimaryKey(); ! if (pk.size() != 1) { ! StringBuffer msg = new StringBuffer(); ! msg ! .append("DB_NAME expressions can only support ") ! .append("targets with a single column PK. ") ! .append("This entity has ") ! .append(pk.size()) ! .append(" columns in primary key."); ! throw new CayenneRuntimeException(msg.toString()); ! } ! att = (DbAttribute) pk.get(0); ! } else { ! att = join.getSource(); ! } ! processColumn(buf, att); ! } } Index: SelectTranslator.java =================================================================== RCS file: /cvsroot/cayenne/cayenne/src/cayenne/java/org/objectstyle/cayenne/access/trans/SelectTranslator.java,v retrieving revision 1.1 retrieving revision 1.2 diff -C2 -d -r1.1 -r1.2 *** SelectTranslator.java 8 Sep 2002 22:46:47 -0000 1.1 --- SelectTranslator.java 1 Oct 2002 02:07:39 -0000 1.2 *************** *** 74,490 **** */ public class SelectTranslator extends SelectQueryAssembler { ! static Logger logObj = Logger.getLogger(SelectTranslator.class.getName()); ! ! private final HashMap aliasLookup = new HashMap(); ! private final ArrayList columnList = new ArrayList(); ! private final ArrayList tableList = new ArrayList(); ! private final ArrayList aliasList = new ArrayList(); ! private final ArrayList dbRelList = new ArrayList(); ! private List groupByList; ! private int aliasCounter; ! /** ! * If set to <code>true</code>, indicates that distinct ! * select query is required no matter what the original query ! * settings where. This flag can be set when joins are created ! * using "to-many" relationships. ! */ ! private boolean forceDistinct; ! /** ! * Returns a list of DbAttributes representing columns ! * in this query. ! */ ! protected List getColumnList() { ! return columnList; ! } ! public int getFetchLimit() { ! return getSelectQuery().getFetchLimit(); ! } ! /** ! * Returns an ordered list of DbAttributes that describe the ! * result columns in the in the ResultSet. ResultSet column names are ignored, ! * names specified in the query are used instead. */ ! public DbAttribute[] getSnapshotDesc(ResultSet rs) { ! int len = columnList.size(); ! if (len == 0) { ! throw new CayenneRuntimeException("Call 'createStatement' first"); ! } ! ! DbAttribute[] desc = new DbAttribute[len]; ! columnList.toArray(desc); ! return desc; ! } ! /** ! * Returns ordered list of Java class names that should be used for fetched values. ! * ResultSet types are ignored, types specified in the query are used instead. ! */ ! public String[] getResultTypes(ResultSet rs) { ! int len = columnList.size(); ! if (len == 0) { ! throw new CayenneRuntimeException("Call 'createStatement' first."); ! } ! String[] types = new String[len]; ! for (int i = 0; i < len; i++) { ! DbAttribute attr = (DbAttribute) columnList.get(i); ! ObjAttribute objAttr = ! getRootEntity().getAttributeForDbAttribute(attr); ! // use explicit type mapping specified in ObjAttribute, ! // or use default JDBC mapping if no ObjAttribute exists ! types[i] = ! (objAttr != null) ! ? objAttr.getType() ! : TypesMapping.getJavaBySqlType(attr.getType()); ! } ! return types; ! } ! /** ! * Returns query translated to SQL. This is a main work method of the SelectTranslator. ! */ ! public String createSqlString() throws Exception { ! forceDistinct = false; ! // build column list ! buildColumnList(); ! QualifierTranslator tr = ! adapter.getQualifierFactory().createTranslator(this); ! // build parent qualifier ! // Parent qualifier translation must PRECEED main qualifier ! // since it will be appended first and its parameters must ! // go first as well ! String parentQualifierStr = null; ! if (getSelectQuery().isQualifiedOnParent()) { ! tr.setTranslateParentQual(true); ! parentQualifierStr = tr.doTranslation(); ! } ! // build main qualifier ! tr.setTranslateParentQual(false); ! String qualifierStr = tr.doTranslation(); ! // build GROUP BY ! buildGroupByList(); ! // build ORDER BY, ! String orderByStr = new OrderingTranslator(this).doTranslation(); ! // assemble ! StringBuffer queryBuf = new StringBuffer(); ! queryBuf.append("SELECT "); ! if (forceDistinct || getSelectQuery().isDistinct()) { ! queryBuf.append("DISTINCT "); ! } ! // append columns (unroll the loop's first element) ! int columnCount = columnList.size(); ! appendColumn(queryBuf, 0); // assume there is at least 1 element ! for (int i = 1; i < columnCount; i++) { ! queryBuf.append(", "); ! appendColumn(queryBuf, i); ! } ! // append from clause ! queryBuf.append(" FROM "); ! // append table list (unroll loop's 1st element) ! int tableCount = tableList.size(); ! appendTable(queryBuf, 0); // assume there is at least 1 table ! for (int i = 1; i < tableCount; i++) { ! queryBuf.append(", "); ! appendTable(queryBuf, i); ! } ! // append db relationship joins if any ! boolean hasWhere = false; ! int dbRelCount = dbRelList.size(); ! if (dbRelCount > 0) { ! hasWhere = true; ! queryBuf.append(" WHERE "); ! appendDbRelJoins(queryBuf, 0); ! for (int i = 1; i < dbRelCount; i++) { ! queryBuf.append(" AND "); ! appendDbRelJoins(queryBuf, i); ! } ! } ! // append parent qualifier if any ! if (parentQualifierStr != null) { ! if (hasWhere) { ! queryBuf.append(" AND ("); ! queryBuf.append(parentQualifierStr); ! queryBuf.append(")"); ! } else { ! hasWhere = true; ! queryBuf.append(" WHERE "); ! queryBuf.append(parentQualifierStr); ! } ! } ! // append group by ! boolean hasGroupBy = false; ! if (groupByList != null) { ! int groupByCount = groupByList.size(); ! if (groupByCount > 0) { ! hasGroupBy = true; ! queryBuf.append(" GROUP BY "); ! appendGroupBy(queryBuf, 0); ! for (int i = 1; i < groupByCount; i++) { ! queryBuf.append(", "); ! appendGroupBy(queryBuf, i); ! } ! } ! } ! // append qualifier ! if (qualifierStr != null) { ! if (hasGroupBy) { ! queryBuf.append(" HAVING "); ! queryBuf.append(qualifierStr); ! } else { ! if (hasWhere) { ! queryBuf.append(" AND ("); ! queryBuf.append(qualifierStr); ! queryBuf.append(")"); ! } else { ! hasWhere = true; ! queryBuf.append(" WHERE "); ! queryBuf.append(qualifierStr); ! } ! } ! } ! // append prebuilt ordering ! if (orderByStr != null) { ! queryBuf.append(" ORDER BY ").append(orderByStr); ! } ! return queryBuf.toString(); ! } ! private SelectQuery getSelectQuery() { ! return (SelectQuery) getQuery(); ! } ! /** ! * Creates a list of columns used in the query. ! */ ! private void buildColumnList() { ! newAliasForTable(getRootEntity().getDbEntity()); ! appendAttributes(); ! } ! /** ! * Creates a list of columns used in the query's GROUP BY clause. ! */ ! private void buildGroupByList() { ! DbEntity dbEntity = getRootEntity().getDbEntity(); ! if (dbEntity instanceof DerivedDbEntity) { ! groupByList = ((DerivedDbEntity) dbEntity).getGroupByAttributes(); ! } ! } ! /** ! * Returns a list of DbAttributes used in query. ! */ ! private void appendAttributes() { ! ObjEntity oe = getRootEntity(); ! DbEntity dbe = oe.getDbEntity(); ! SelectQuery q = getSelectQuery(); ! // extract custom attributes from the query ! if (q.isFetchingCustAttributes()) { ! List custAttrNames = q.getCustDbAttributes(); ! int len = custAttrNames.size(); ! for (int i = 0; i < len; i++) { ! Attribute attr = ! dbe.getAttribute((String) custAttrNames.get(i)); ! if (attr == null) { ! throw new CayenneRuntimeException( ! "Attribute does not exist: " + custAttrNames.get(i)); ! } ! columnList.add(attr); ! } ! } else { ! // build a list of attributes mentioned in ObjEntity + PK's + FK's + GROUP BY's ! // ObjEntity attrs ! List attrs = oe.getAttributeList(); ! int len = attrs.size(); ! for (int i = 0; i < len; i++) { ! ObjAttribute oa = (ObjAttribute) attrs.get(i); ! Attribute dbAttr = oa.getDbAttribute(); ! if (dbAttr == null) { ! throw new CayenneRuntimeException( ! "ObjAttribute has no DbAttribute: " + oa.getName()); ! } ! columnList.add(dbAttr); ! } ! // relationship keys ! List rels = oe.getRelationshipList(); ! int rLen = rels.size(); ! for (int i = 0; i < rLen; i++) { ! ObjRelationship rel = (ObjRelationship) rels.get(i); ! DbRelationship dbRel = ! (DbRelationship) rel.getDbRelationshipList().get(0); ! List joins = dbRel.getJoins(); ! int jLen = joins.size(); ! for (int j = 0; j < jLen; j++) { ! DbAttributePair join = (DbAttributePair) joins.get(j); ! DbAttribute src = join.getSource(); ! if (!columnList.contains(src)) { ! columnList.add(src); ! } ! } ! } ! // add remaining needed attrs from DbEntity ! List dbattrs = dbe.getAttributeList(); ! int dLen = dbattrs.size(); ! for (int i = 0; i < dLen; i++) { ! DbAttribute dba = (DbAttribute) dbattrs.get(i); ! if (dba.isPrimaryKey()) { ! if (!columnList.contains(dba)) { ! columnList.add(dba); ! } ! } ! } ! } ! } ! private void appendColumn(StringBuffer queryBuf, int index) { ! DbAttribute attr = (DbAttribute) columnList.get(index); ! String alias = aliasForTable((DbEntity) attr.getEntity()); ! queryBuf.append(attr.getAliasedName(alias)); ! } ! private void appendGroupBy(StringBuffer queryBuf, int index) { ! DbAttribute attr = (DbAttribute) groupByList.get(index); ! DbEntity ent = (DbEntity)attr.getEntity(); ! queryBuf.append( ! attr.getAliasedName(aliasForTable(ent))); ! } ! private void appendTable(StringBuffer queryBuf, int index) { ! DbEntity ent = (DbEntity) tableList.get(index); ! queryBuf.append(ent.getFullyQualifiedName()); ! queryBuf.append(' ').append(aliasForTable(ent)); ! } ! private void appendDbRelJoins(StringBuffer queryBuf, int index) { ! DbRelationship rel = (DbRelationship) dbRelList.get(index); ! String srcAlias = aliasForTable((DbEntity) rel.getSourceEntity()); ! String targetAlias = aliasForTable((DbEntity) rel.getTargetEntity()); ! boolean andFlag = false; ! List joins = rel.getJoins(); ! int len = joins.size(); ! for (int i = 0; i < len; i++) { ! if (andFlag) ! queryBuf.append(" AND "); ! else ! andFlag = true; ! DbAttributePair join = (DbAttributePair) joins.get(i); ! DbAttribute src = join.getSource(); ! queryBuf ! .append(srcAlias) ! .append('.') ! .append(join.getSource().getName()) ! .append(" = ") ! .append(targetAlias) ! .append('.') ! .append(join.getTarget().getName()); ! } ! } ! /** ! * Stores a new relationship in an internal list. ! * Later it will be used to create joins to relationship ! * destination table. ! */ ! public void dbRelationshipAdded(DbRelationship rel) { ! if (rel.isToMany()) { ! forceDistinct = true; ! } ! String existAlias = (String) aliasLookup.get(rel); ! if (existAlias == null) { ! dbRelList.add(rel); ! // add alias for the destination table of the relationship ! String newAlias = ! newAliasForTable((DbEntity) rel.getTargetEntity()); ! aliasLookup.put(rel, newAlias); ! } ! } ! /** ! * Sets up and returns a new alias for a speciafied table. ! */ ! protected String newAliasForTable(DbEntity ent) { ! if (ent instanceof DerivedDbEntity) { ! ent = ((DerivedDbEntity) ent).getParentEntity(); ! } ! String newAlias = "t" + aliasCounter++; ! tableList.add(ent); ! aliasList.add(newAlias); ! return newAlias; ! } ! /** ! * Overrides superclass implementation. Will return an alias that ! * should be used for a specified DbEntity in the query ! * (or null if this DbEntity is not included in the FROM clause). ! */ ! public String aliasForTable(DbEntity ent) { ! if (ent instanceof DerivedDbEntity) { ! ent = ((DerivedDbEntity) ent).getParentEntity(); ! } ! int entIndex = tableList.indexOf(ent); ! if (entIndex >= 0) { ! return (String) aliasList.get(entIndex); ! } else { ! StringBuffer msg = new StringBuffer(); ! msg ! .append("Alias not found, DbEntity: '") ! .append(ent != null ? ent.getName() : "<null entity>") ! .append("'\nExisting aliases:"); ! int len = aliasList.size(); ! for (int i = 0; i < len; i++) { ! String dbeName = ! (tableList.get(i) != null) ! ? ((DbEntity) tableList.get(i)).getName() ! : "<null entity>"; ! msg.append("\n").append(aliasList.get(0)).append( ! " => ").append( ! dbeName); ! } ! throw new CayenneRuntimeException(msg.toString()); ! } ! } ! public boolean supportsTableAliases() { ! return true; ! } } --- 74,486 ---- */ public class SelectTranslator extends SelectQueryAssembler { ! static Logger logObj = Logger.getLogger(SelectTranslator.class.getName()); ! private final HashMap aliasLookup = new HashMap(); ! private final ArrayList columnList = new ArrayList(); ! private final ArrayList tableList = new ArrayList(); ! private final ArrayList aliasList = new ArrayList(); ! private final ArrayList dbRelList = new ArrayList(); ! private List groupByList; ! private int aliasCounter; ! /** ! * If set to <code>true</code>, indicates that distinct ! * select query is required no matter what the original query ! * settings where. This flag can be set when joins are created ! * using "to-many" relationships. ! */ ! private boolean forceDistinct; ! /** ! * Returns a list of DbAttributes representing columns ! * in this query. ! */ ! protected List getColumnList() { ! return columnList; ! } ! public int getFetchLimit() { ! return getSelectQuery().getFetchLimit(); ! } ! /** ! * Returns an ordered list of DbAttributes that describe the ! * result columns in the in the ResultSet. ResultSet column names are ignored, ! * names specified in the query are used instead. */ ! public DbAttribute[] getSnapshotDesc(ResultSet rs) { ! int len = columnList.size(); ! if (len == 0) { ! throw new CayenneRuntimeException("Call 'createStatement' first"); ! } ! DbAttribute[] desc = new DbAttribute[len]; ! columnList.toArray(desc); ! return desc; ! } ! /** ! * Returns ordered list of Java class names that should be used for fetched values. ! * ResultSet types are ignored, types specified in the query are used instead. ! */ ! public String[] getResultTypes(ResultSet rs) { ! int len = columnList.size(); ! if (len == 0) { ! throw new CayenneRuntimeException("Call 'createStatement' first."); ! } ! String[] types = new String[len]; ! for (int i = 0; i < len; i++) { ! DbAttribute attr = (DbAttribute) columnList.get(i); ! ObjAttribute objAttr = getRootEntity().getAttributeForDbAttribute(attr); ! // use explicit type mapping specified in ObjAttribute, ! // or use default JDBC mapping if no ObjAttribute exists ! types[i] = ! (objAttr != null) ! ? objAttr.getType() ! : TypesMapping.getJavaBySqlType(attr.getType()); ! } ! return types; ! } ! /** ! * Returns query translated to SQL. This is a main work method of the SelectTranslator. ! */ ! public String createSqlString() throws Exception { ! forceDistinct = false; ! // build column list ! buildColumnList(); ! QualifierTranslator tr = adapter.getQualifierFactory().createTranslator(this); ! // build parent qualifier ! // Parent qualifier translation must PRECEED main qualifier ! // since it will be appended first and its parameters must ! // go first as well ! String parentQualifierStr = null; ! if (getSelectQuery().isQualifiedOnParent()) { ! tr.setTranslateParentQual(true); ! parentQualifierStr = tr.doTranslation(); ! } ! // build main qualifier ! tr.setTranslateParentQual(false); ! String qualifierStr = tr.doTranslation(); ! // build GROUP BY ! buildGroupByList(); ! // build ORDER BY, ! String orderByStr = new OrderingTranslator(this).doTranslation(); ! // assemble ! StringBuffer queryBuf = new StringBuffer(); ! queryBuf.append("SELECT "); ! if (forceDistinct || getSelectQuery().isDistinct()) { ! queryBuf.append("DISTINCT "); ! } ! // append columns (unroll the loop's first element) ! int columnCount = columnList.size(); ! appendColumn(queryBuf, 0); // assume there is at least 1 element ! for (int i = 1; i < columnCount; i++) { ! queryBuf.append(", "); ! appendColumn(queryBuf, i); ! } ! // append from clause ! queryBuf.append(" FROM "); ! // append table list (unroll loop's 1st element) ! int tableCount = tableList.size(); ! appendTable(queryBuf, 0); // assume there is at least 1 table ! for (int i = 1; i < tableCount; i++) { ! queryBuf.append(", "); ! appendTable(queryBuf, i); ! } ! // append db relationship joins if any ! boolean hasWhere = false; ! int dbRelCount = dbRelList.size(); ! if (dbRelCount > 0) { ! hasWhere = true; ! queryBuf.append(" WHERE "); ! appendDbRelJoins(queryBuf, 0); ! for (int i = 1; i < dbRelCount; i++) { ! queryBuf.append(" AND "); ! appendDbRelJoins(queryBuf, i); ! } ! } ! // append parent qualifier if any ! if (parentQualifierStr != null) { ! if (hasWhere) { ! queryBuf.append(" AND ("); ! queryBuf.append(parentQualifierStr); ! queryBuf.append(")"); ! } else { ! hasWhere = true; ! queryBuf.append(" WHERE "); ! queryBuf.append(parentQualifierStr); ! } ! } ! // append group by ! boolean hasGroupBy = false; ! if (groupByList != null) { ! int groupByCount = groupByList.size(); ! if (groupByCount > 0) { ! hasGroupBy = true; ! queryBuf.append(" GROUP BY "); ! appendGroupBy(queryBuf, 0); ! for (int i = 1; i < groupByCount; i++) { ! queryBuf.append(", "); ! appendGroupBy(queryBuf, i); ! } ! } ! } ! // append qualifier ! if (qualifierStr != null) { ! if (hasGroupBy) { ! queryBuf.append(" HAVING "); ! queryBuf.append(qualifierStr); ! } else { ! if (hasWhere) { ! queryBuf.append(" AND ("); ! queryBuf.append(qualifierStr); ! queryBuf.append(")"); ! } else { ! hasWhere = true; ! queryBuf.append(" WHERE "); ! queryBuf.append(qualifierStr); ! } ! } ! } ! // append prebuilt ordering ! if (orderByStr != null) { ! queryBuf.append(" ORDER BY ").append(orderByStr); ! } ! return queryBuf.toString(); ! } ! private SelectQuery getSelectQuery() { ! return (SelectQuery) getQuery(); ! } ! /** ! * Creates a list of columns used in the query. ! */ ! private void buildColumnList() { ! newAliasForTable(getRootEntity().getDbEntity()); ! appendAttributes(); ! } ! /** ! * Creates a list of columns used in the query's GROUP BY clause. ! */ ! private void buildGroupByList() { ! DbEntity dbEntity = getRootEntity().getDbEntity(); ! if (dbEntity instanceof DerivedDbEntity) { ! groupByList = ((DerivedDbEntity) dbEntity).getGroupByAttributes(); ! } ! } ! /** ! * Returns a list of DbAttributes used in query. ! */ ! private void appendAttributes() { ! ObjEntity oe = getRootEntity(); ! DbEntity dbe = oe.getDbEntity(); ! SelectQuery q = getSelectQuery(); ! // extract custom attributes from the query ! if (q.isFetchingCustAttributes()) { ! List custAttrNames = q.getCustDbAttributes(); ! int len = custAttrNames.size(); ! for (int i = 0; i < len; i++) { ! Attribute attr = dbe.getAttribute((String) custAttrNames.get(i)); ! if (attr == null) { ! throw new CayenneRuntimeException( ! "Attribute does not exist: " + custAttrNames.get(i)); ! } ! columnList.add(attr); ! } ! } else { ! // build a list of attributes mentioned in ObjEntity + PK's + FK's + GROUP BY's ! // ObjEntity attrs ! List attrs = oe.getAttributeList(); ! int len = attrs.size(); ! for (int i = 0; i < len; i++) { ! ObjAttribute oa = (ObjAttribute) attrs.get(i); ! Attribute dbAttr = oa.getDbAttribute(); ! if (dbAttr == null) { ! throw new CayenneRuntimeException( ! "ObjAttribute has no DbAttribute: " + oa.getName()); ! } ! columnList.add(dbAttr); ! } ! // relationship keys ! List rels = oe.getRelationshipList(); ! int rLen = rels.size(); ! for (int i = 0; i < rLen; i++) { ! ObjRelationship rel = (ObjRelationship) rels.get(i); ! DbRelationship dbRel = ! (DbRelationship) rel.getDbRelationshipList().get(0); ! List joins = dbRel.getJoins(); ! int jLen = joins.size(); ! for (int j = 0; j < jLen; j++) { ! DbAttributePair join = (DbAttributePair) joins.get(j); ! DbAttribute src = join.getSource(); ! if (!columnList.contains(src)) { ! columnList.add(src); ! } ! } ! } + // add remaining needed attrs from DbEntity + List dbattrs = dbe.getAttributeList(); + int dLen = dbattrs.size(); + for (int i = 0; i < dLen; i++) { + DbAttribute dba = (DbAttribute) dbattrs.get(i); + if (dba.isPrimaryKey()) { + if (!columnList.contains(dba)) { + columnList.add(dba); + } + } + } + } + } ! private void appendColumn(StringBuffer queryBuf, int index) { ! DbAttribute attr = (DbAttribute) columnList.get(index); ! String alias = aliasForTable((DbEntity) attr.getEntity()); ! queryBuf.append(attr.getAliasedName(alias)); ! } + private void appendGroupBy(StringBuffer queryBuf, int index) { + DbAttribute attr = (DbAttribute) groupByList.get(index); + DbEntity ent = (DbEntity) attr.getEntity(); + queryBuf.append(attr.getAliasedName(aliasForTable(ent))); + } ! private void appendTable(StringBuffer queryBuf, int index) { ! DbEntity ent = (DbEntity) tableList.get(index); ! queryBuf.append(ent.getFullyQualifiedName()); ! //The alias should be the alias from the same index in aliasList, not that ! // returned by aliasForTable. ! queryBuf.append(' ').append((String) aliasList.get(index)); ! } + private void appendDbRelJoins(StringBuffer queryBuf, int index) { + DbRelationship rel = (DbRelationship) dbRelList.get(index); + String srcAlias = aliasForTable((DbEntity) rel.getSourceEntity()); + String targetAlias = (String) aliasLookup.get(rel); ! boolean andFlag = false; ! List joins = rel.getJoins(); ! int len = joins.size(); ! for (int i = 0; i < len; i++) { ! if (andFlag) ! queryBuf.append(" AND "); ! else ! andFlag = true; ! DbAttributePair join = (DbAttributePair) joins.get(i); ! DbAttribute src = join.getSource(); ! queryBuf ! .append(srcAlias) ! .append('.') ! .append(join.getSource().getName()) ! .append(" = ") ! .append(targetAlias) ! .append('.') ! .append(join.getTarget().getName()); ! } ! } ! /** ! * Stores a new relationship in an internal list. ! * Later it will be used to create joins to relationship ! * destination table. ! */ ! public void dbRelationshipAdded(DbRelationship rel) { ! if (rel.isToMany()) { ! forceDistinct = true; ! } ! String existAlias = (String) aliasLookup.get(rel); ! if (existAlias == null) { ! dbRelList.add(rel); ! // add alias for the destination table of the relationship ! String newAlias = newAliasForTable((DbEntity) rel.getTargetEntity()); ! aliasLookup.put(rel, newAlias); ! } ! } ! /** ! * Sets up and returns a new alias for a speciafied table. ! */ ! protected String newAliasForTable(DbEntity ent) { ! if (ent instanceof DerivedDbEntity) { ! ent = ((DerivedDbEntity) ent).getParentEntity(); ! } ! String newAlias = "t" + aliasCounter++; ! tableList.add(ent); ! aliasList.add(newAlias); ! return newAlias; ! } ! public String aliasForTable(DbEntity ent, DbRelationship rel) { ! return (String) aliasLookup.get(rel); ! } ! /** ! * Overrides superclass implementation. Will return an alias that ! * should be used for a specified DbEntity in the query ! * (or null if this DbEntity is not included in the FROM clause). ! */ ! public String aliasForTable(DbEntity ent) { ! if (ent instanceof DerivedDbEntity) { ! ent = ((DerivedDbEntity) ent).getParentEntity(); ! } ! int entIndex = tableList.indexOf(ent); ! if (entIndex >= 0) { ! return (String) aliasList.get(entIndex); ! } else { ! StringBuffer msg = new StringBuffer(); ! msg ! .append("Alias not found, DbEntity: '") ! .append(ent != null ? ent.getName() : "<null entity>") ! .append("'\nExisting aliases:"); ! int len = aliasList.size(); ! for (int i = 0; i < len; i++) { ! String dbeName = ! (tableList.get(i) != null) ! ? ((DbEntity) tableList.get(i)).getName() ! : "<null entity>"; ! msg.append("\n").append(aliasList.get(0)).append(" => ").append(dbeName); ! } ! throw new CayenneRuntimeException(msg.toString()); ! } ! } ! public boolean supportsTableAliases() { ! return true; ! } } |