http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java new file mode 100644 index 0000000..b330608 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathProcessor.java @@ -0,0 +1,129 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.access.translator.select; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +import org.apache.cayenne.map.DbAttribute; +import org.apache.cayenne.map.DbRelationship; +import org.apache.cayenne.map.Entity; + +/** + * @since 4.2 + */ +abstract class PathProcessor<T extends Entity> implements PathTranslationResult { + + public static final char OUTER_JOIN_INDICATOR = '+'; + public static final char SPLIT_PATH_INDICATOR = '#'; + + protected final Map<String, String> pathSplitAliases; + protected final TranslatorContext context; + protected final List<String> attributePaths; + protected final List<DbAttribute> attributes; + protected final StringBuilder currentDbPath; + + protected boolean lastComponent; + protected boolean isOuterJoin; + protected T entity; + protected DbRelationship relationship; + protected String currentAlias; + + public PathProcessor(TranslatorContext context, T entity) { + this.context = Objects.requireNonNull(context); + this.entity = Objects.requireNonNull(entity); + this.pathSplitAliases = context.getMetadata().getPathSplitAliases(); + this.currentDbPath = new StringBuilder(); + this.attributes = new ArrayList<>(1); + this.attributePaths = new ArrayList<>(1); + } + + public PathTranslationResult process(String path) { + PathComponents components = new PathComponents(path); + String[] rawComponents = components.getAll(); + for(int i=0; i<rawComponents.length; i++) { + String next = rawComponents[i]; + isOuterJoin = false; + lastComponent = i == rawComponents.length - 1; + String alias = pathSplitAliases.get(next); + if(alias != null) { + currentAlias = next; + processAliasedAttribute(next, alias); + currentAlias = null; + } else { + if(next.charAt(next.length() - 1) == OUTER_JOIN_INDICATOR) { + isOuterJoin = true; + next = next.substring(0, next.length() - 1); + } + processNormalAttribute(next); + } + } + + return this; + } + + protected void addAttribute(String path, DbAttribute attribute) { + attributePaths.add(path); + attributes.add(attribute); + } + + abstract protected void processAliasedAttribute(String next, String alias); + + abstract protected void processNormalAttribute(String next); + + @Override + public List<DbAttribute> getDbAttributes() { + return attributes; + } + + @Override + public List<String> getAttributePaths() { + return attributePaths; + } + + @Override + public Optional<DbRelationship> getDbRelationship() { + if(relationship == null) { + return Optional.empty(); + } + return Optional.of(relationship); + } + + @Override + public String getFinalPath() { + return currentDbPath.toString(); + } + + protected void appendCurrentPath(String nextSegment) { + if(currentDbPath.length() > 0) { + currentDbPath.append('.'); + } + currentDbPath.append(nextSegment); + if(currentAlias != null) { + currentDbPath.append(SPLIT_PATH_INDICATOR).append(currentAlias); + } + if(isOuterJoin) { + currentDbPath.append(OUTER_JOIN_INDICATOR); + } + } +}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java new file mode 100644 index 0000000..f4158de --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslationResult.java @@ -0,0 +1,51 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.access.translator.select; + +import java.util.List; +import java.util.Optional; + +import org.apache.cayenne.map.DbAttribute; +import org.apache.cayenne.map.DbRelationship; + +/** + * Interface that describes result of path translation + * + * @since 4.2 + */ +interface PathTranslationResult { + + String getFinalPath(); + + Optional<DbRelationship> getDbRelationship(); + + List<DbAttribute> getDbAttributes(); + + List<String> getAttributePaths(); + + default DbAttribute getLastAttribute() { + return getDbAttributes().get(getDbAttributes().size() - 1); + } + + default String getLastAttributePath() { + return getAttributePaths().get(getAttributePaths().size() - 1); + } + +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslator.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslator.java new file mode 100644 index 0000000..c3dd57f --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PathTranslator.java @@ -0,0 +1,60 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.access.translator.select; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.map.ObjEntity; + +/** + * @since 4.2 + */ +class PathTranslator { + + private final Map<String, PathTranslationResult> objResultCache = new ConcurrentHashMap<>(); + private final Map<String, PathTranslationResult> dbResultCache = new ConcurrentHashMap<>(); + + private final TranslatorContext context; + + PathTranslator(TranslatorContext context) { + this.context = context; + } + + PathTranslationResult translatePath(ObjEntity entity, String path, String parentPath) { + return objResultCache.computeIfAbsent(parentPath + '.' + entity.getName() + '.' + path, + (k) -> new ObjPathProcessor(context, entity, parentPath).process(path)); + } + + PathTranslationResult translatePath(ObjEntity entity, String path) { + return translatePath(entity, path, null); + } + + PathTranslationResult translatePath(DbEntity entity, String path, String parentPath) { + return dbResultCache.computeIfAbsent(parentPath + '.' + entity.getName() + '.' + path, + (k) -> new DbPathProcessor(context, entity, parentPath).process(path)); + } + + PathTranslationResult translatePath(DbEntity entity, String path) { + return translatePath(entity, path, null); + } + +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java new file mode 100644 index 0000000..90c0f3d --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/PrefetchNodeStage.java @@ -0,0 +1,128 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.access.translator.select; + +import org.apache.cayenne.CayenneRuntimeException; +import org.apache.cayenne.access.sqlbuilder.sqltree.Node; +import org.apache.cayenne.exp.Expression; +import org.apache.cayenne.exp.ExpressionFactory; +import org.apache.cayenne.exp.parser.ASTDbPath; +import org.apache.cayenne.map.DbAttribute; +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.map.DbRelationship; +import org.apache.cayenne.map.JoinType; +import org.apache.cayenne.map.ObjEntity; +import org.apache.cayenne.map.ObjRelationship; +import org.apache.cayenne.query.PrefetchSelectQuery; +import org.apache.cayenne.query.PrefetchTreeNode; +import org.apache.cayenne.query.Select; +import org.apache.cayenne.reflect.ClassDescriptor; + +import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.table; + +/** + * @since 4.2 + */ +class PrefetchNodeStage implements TranslationStage { + + @Override + public void perform(TranslatorContext context) { + updatePrefetchNodes(context); + processJoint(context); + processPrefetchQuery(context); + } + + private void updatePrefetchNodes(TranslatorContext context) { + if(context.getQuery().getPrefetchTree() == null) { + return; + } + // Set entity name, in case MixedConversionStrategy will be used to select objects from this query + // Note: all prefetch nodes will point to query root, it is not a problem until select query can't + // perform some sort of union or sub-queries. + for(PrefetchTreeNode prefetch : context.getQuery().getPrefetchTree().getChildren()) { + prefetch.setEntityName(context.getMetadata().getObjEntity().getName()); + } + } + + private void processJoint(TranslatorContext context) { + PrefetchTreeNode prefetch = context.getQuery().getPrefetchTree(); + if(prefetch == null) { + return; + } + + ObjEntity objEntity = context.getMetadata().getObjEntity(); + + for(PrefetchTreeNode node : prefetch.adjacentJointNodes()) { + Expression prefetchExp = ExpressionFactory.exp(node.getPath()); + ASTDbPath dbPrefetch = (ASTDbPath) objEntity.translateToDbPath(prefetchExp); + final String dbPath = dbPrefetch.getPath(); + DbEntity dbEntity = objEntity.getDbEntity(); + + PathComponents components = new PathComponents(dbPath); + StringBuilder fullPath = new StringBuilder(); + for(String c : components.getAll()) { + DbRelationship rel = dbEntity.getRelationship(c); + if(rel == null) { + throw new CayenneRuntimeException("Unable to resolve path %s for entity %s", dbPath, objEntity.getName()); + } + if(fullPath.length() > 0) { + fullPath.append('.'); + } + context.getTableTree().addJoinTable("p:" + fullPath.append(c).toString(), rel, JoinType.LEFT_OUTER); + dbEntity = rel.getTargetEntity(); + } + + ObjRelationship targetRel = (ObjRelationship) prefetchExp.evaluate(objEntity); + ClassDescriptor prefetchClassDescriptor = context.getResolver().getClassDescriptor(targetRel.getTargetEntityName()); + + DescriptorColumnExtractor columnExtractor = new DescriptorColumnExtractor(context, prefetchClassDescriptor); + columnExtractor.extract("p:" + dbPath); + } + } + + private void processPrefetchQuery(TranslatorContext context) { + Select<?> select = context.getQuery().unwrap(); + if(!(select instanceof PrefetchSelectQuery)) { + return; + } + + PathTranslator pathTranslator = context.getPathTranslator(); + PrefetchSelectQuery<?> prefetchSelectQuery = (PrefetchSelectQuery<?>) select; + for(String prefetchPath: prefetchSelectQuery.getResultPaths()) { + ASTDbPath pathExp = (ASTDbPath) context.getMetadata().getClassDescriptor().getEntity() + .translateToDbPath(ExpressionFactory.exp(prefetchPath)); + + String path = pathExp.getPath(); + PathTranslationResult result = pathTranslator + .translatePath(context.getMetadata().getDbEntity(), path); + result.getDbRelationship().ifPresent(r -> { + DbEntity targetEntity = r.getTargetEntity(); + context.getTableTree().addJoinTable(path, r, JoinType.INNER); + for (DbAttribute pk : targetEntity.getPrimaryKeys()) { + // note that we may select a source attribute, but label it as target for simplified snapshot processing + String finalPath = path + '.' + pk.getName(); + String alias = context.getTableTree().aliasForPath(path); + Node columnNode = table(alias).column(pk).build(); + context.addResultNode(columnNode, finalPath).setDbAttribute(pk); + } + }); + } + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslationStage.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslationStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslationStage.java new file mode 100644 index 0000000..c4d2ba1 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslationStage.java @@ -0,0 +1,74 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.access.translator.select; + +import org.apache.cayenne.access.sqlbuilder.sqltree.Node; +import org.apache.cayenne.exp.Expression; +import org.apache.cayenne.exp.parser.ASTDbPath; +import org.apache.cayenne.exp.parser.ASTObjPath; +import org.apache.cayenne.exp.parser.SimpleNode; +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.map.ObjEntity; +import org.apache.cayenne.reflect.ClassDescriptor; + +/** + * @since 4.2 + */ +class QualifierTranslationStage implements TranslationStage { + + @Override + public void perform(TranslatorContext context) { + QualifierTranslator translator = context.getQualifierTranslator(); + + Expression expression = context.getQuery().getQualifier(); + + // Attaching Obj entity's qualifier + ObjEntity entity = context.getMetadata().getObjEntity(); + if (entity != null) { + ClassDescriptor descriptor = context.getMetadata().getClassDescriptor(); + Expression entityQualifier = descriptor.getEntityInheritanceTree().qualifierForEntityAndSubclasses(); + if (entityQualifier != null) { + expression = expression == null ? entityQualifier : expression.andExp(entityQualifier); + } + } + + // Attaching root Db entity's qualifier + DbEntity dbEntity = context.getMetadata().getDbEntity(); + if (dbEntity != null) { + Expression dbQualifier = dbEntity.getQualifier(); + if (dbQualifier != null) { + dbQualifier = dbQualifier.transform(node -> { + if (node instanceof ASTObjPath) { + return new ASTDbPath(((SimpleNode) node).getOperand(0)); + } + return node; + }); + + expression = expression == null ? dbQualifier : expression.andExp(dbQualifier); + } + } + + Node qualifierNode = translator.translate(expression); + + if(qualifierNode != null) { + context.getSelectBuilder().where(qualifierNode); + } + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java index 3795976..d48a53f 100644 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QualifierTranslator.java @@ -19,672 +19,394 @@ package org.apache.cayenne.access.translator.select; -import java.io.IOException; -import java.util.Arrays; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.HashSet; import java.util.Iterator; -import java.util.List; -import java.util.function.Function; +import java.util.Set; import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.ObjectId; import org.apache.cayenne.Persistent; -import org.apache.cayenne.dba.TypesMapping; +import org.apache.cayenne.access.sqlbuilder.ExpressionNodeBuilder; +import org.apache.cayenne.access.sqlbuilder.ValueNodeBuilder; +import org.apache.cayenne.access.sqlbuilder.sqltree.BetweenNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.BitwiseNotNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.EmptyNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.EqualNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.FunctionNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.InNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.LikeNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.Node; +import org.apache.cayenne.access.sqlbuilder.sqltree.NotEqualNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.NotNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.OpExpressionNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.TextNode; import org.apache.cayenne.exp.Expression; import org.apache.cayenne.exp.TraversalHandler; import org.apache.cayenne.exp.parser.ASTDbPath; -import org.apache.cayenne.exp.parser.ASTExtract; +import org.apache.cayenne.exp.parser.ASTFullObject; import org.apache.cayenne.exp.parser.ASTFunctionCall; import org.apache.cayenne.exp.parser.ASTObjPath; +import org.apache.cayenne.exp.parser.ASTSubquery; import org.apache.cayenne.exp.parser.PatternMatchNode; import org.apache.cayenne.exp.parser.SimpleNode; +import org.apache.cayenne.exp.property.BaseProperty; import org.apache.cayenne.map.DbAttribute; -import org.apache.cayenne.map.DbRelationship; -import org.apache.cayenne.map.JoinType; -import org.apache.cayenne.map.ObjEntity; -import org.apache.cayenne.query.Query; -import org.apache.cayenne.query.SelectQuery; -import org.apache.cayenne.reflect.ClassDescriptor; + +import static org.apache.cayenne.access.sqlbuilder.SQLBuilder.*; +import static org.apache.cayenne.exp.Expression.*; /** - * Translates query qualifier to SQL. Used as a helper class by query - * translators. + * @since 4.2 */ -public class QualifierTranslator extends QueryAssemblerHelper implements TraversalHandler { - - protected DataObjectMatchTranslator objectMatchTranslator; - protected boolean matchingObject; - protected boolean caseInsensitive; - - /** - * @since 4.0 - */ - protected boolean useAliasForExpressions; - - /** - * @since 4.0 - */ - protected Expression waitingForEndNode; - - /** - * @since 4.0 - */ - protected Expression qualifier; - - public QualifierTranslator(QueryAssembler queryAssembler) { - super(queryAssembler); - - caseInsensitive = false; - } - - /** - * Translates query qualifier to SQL WHERE clause. Qualifier is obtained - * from the parent queryAssembler. - * - * @since 3.0 - */ - @Override - protected void doAppendPart() { - doAppendPart(extractQualifier()); - } - - public void setCaseInsensitive(boolean caseInsensitive) { - this.caseInsensitive = caseInsensitive; - } - - /** - * Explicitly set qualifier. - * It will be used instead of extracting qualifier from the query itself. - * @since 4.0 - */ - public void setQualifier(Expression qualifier) { - this.qualifier = qualifier; - } - - /** - * @since 4.0 - */ - public void setUseAliasForExpressions(boolean useAliasForExpressions) { - this.useAliasForExpressions = useAliasForExpressions; - } - - /** - * Translates query qualifier to SQL WHERE clause. Qualifier is a method - * parameter. - * - * @since 3.0 - */ - protected void doAppendPart(Expression rootNode) { - if (rootNode == null) { - return; - } - rootNode.traverse(this); - } - - protected Expression extractQualifier() { - // if additional qualifier is set, use it - if(this.qualifier != null) { - return this.qualifier; - } - - Query q = queryAssembler.getQuery(); - - Expression qualifier = ((SelectQuery<?>) q).getQualifier(); - - // append Entity qualifiers, taking inheritance into account - ObjEntity entity = getObjEntity(); - - if (entity != null) { - - ClassDescriptor descriptor = queryAssembler.getEntityResolver().getClassDescriptor(entity.getName()); - Expression entityQualifier = descriptor.getEntityInheritanceTree().qualifierForEntityAndSubclasses(); - if (entityQualifier != null) { - qualifier = (qualifier != null) ? qualifier.andExp(entityQualifier) : entityQualifier; - } - } - - // Attaching root Db entity's qualifier - if (getDbEntity() != null) { - Expression dbQualifier = getDbEntity().getQualifier(); - if (dbQualifier != null) { - dbQualifier = dbQualifier.transform(new DbEntityQualifierTransformer()); - - qualifier = qualifier == null ? dbQualifier : qualifier.andExp(dbQualifier); - } - } - - return qualifier; - } - - /** - * Called before processing an expression to initialize - * objectMatchTranslator if needed. - */ - protected void detectObjectMatch(Expression exp) { - // On demand initialization of - // objectMatchTranslator is not possible since there may be null - // object values that would not allow to detect the need for - // such translator in the right time (e.g.: null = dbpath) - - matchingObject = false; - - if (exp.getOperandCount() != 2) { - // only binary expressions are supported - return; - } - - // check if there are DataObjects among direct children of the - // Expression - for (int i = 0; i < 2; i++) { - Object op = exp.getOperand(i); - if (op instanceof Persistent || op instanceof ObjectId) { - matchingObject = true; - - if (objectMatchTranslator == null) { - objectMatchTranslator = new DataObjectMatchTranslator(); - } else { - objectMatchTranslator.reset(); - } - break; - } - } - } - - protected void appendObjectMatch() throws IOException { - if (!matchingObject || objectMatchTranslator == null) { - throw new IllegalStateException("An invalid attempt to append object match."); - } - - // turn off special handling, so that all the methods behave as a - // superclass's - // impl. - matchingObject = false; - - boolean first = true; - - DbRelationship relationship = objectMatchTranslator.getRelationship(); - if (!relationship.isToMany() && !relationship.isToPK()) { - queryAssembler.dbRelationshipAdded(relationship, JoinType.INNER, objectMatchTranslator.getJoinSplitAlias()); - } - - Iterator<String> it = objectMatchTranslator.keys(); - while (it.hasNext()) { - if (first) { - first = false; - } else { - out.append(" AND "); - } - - String key = it.next(); - DbAttribute attr = objectMatchTranslator.getAttribute(key); - Object val = objectMatchTranslator.getValue(key); - - processColumn(attr); - out.append(objectMatchTranslator.getOperation()); - appendLiteral(val, attr, objectMatchTranslator.getExpression()); - } - - objectMatchTranslator.reset(); - } - - @Override - public void finishedChild(Expression node, int childIndex, boolean hasMoreChildren) { - - if(waitingForEndNode != null) { - return; - } - - if (!hasMoreChildren) { - return; - } - - Appendable out = (matchingObject) ? new StringBuilder() : this.out; - - try { - switch (node.getType()) { - case Expression.AND: - out.append(" AND "); - break; - case Expression.OR: - out.append(" OR "); - break; - case Expression.EQUAL_TO: - // translate NULL as IS NULL - if (childIndex == 0 && node.getOperandCount() == 2 && node.getOperand(1) == null) { - out.append(" IS "); - } else { - out.append(" = "); - } - break; - case Expression.NOT_EQUAL_TO: - // translate NULL as IS NOT NULL - if (childIndex == 0 && node.getOperandCount() == 2 && node.getOperand(1) == null) { - out.append(" IS NOT "); - } else { - out.append(" <> "); - } - break; - case Expression.LESS_THAN: - out.append(" < "); - break; - case Expression.GREATER_THAN: - out.append(" > "); - break; - case Expression.LESS_THAN_EQUAL_TO: - out.append(" <= "); - break; - case Expression.GREATER_THAN_EQUAL_TO: - out.append(" >= "); - break; - case Expression.IN: - out.append(" IN "); - break; - case Expression.NOT_IN: - out.append(" NOT IN "); - break; - case Expression.LIKE: - out.append(" LIKE "); - break; - case Expression.NOT_LIKE: - out.append(" NOT LIKE "); - break; - case Expression.LIKE_IGNORE_CASE: - if (caseInsensitive) { - out.append(" LIKE "); - } else { - out.append(") LIKE UPPER("); - } - break; - case Expression.NOT_LIKE_IGNORE_CASE: - if (caseInsensitive) { - out.append(" NOT LIKE "); - } else { - out.append(") NOT LIKE UPPER("); - } - break; - case Expression.ADD: - out.append(" + "); - break; - case Expression.SUBTRACT: - out.append(" - "); - break; - case Expression.MULTIPLY: - out.append(" * "); - break; - case Expression.DIVIDE: - out.append(" / "); - break; - case Expression.BETWEEN: - if (childIndex == 0) { - out.append(" BETWEEN "); - } else if (childIndex == 1) { - out.append(" AND "); - } - break; - case Expression.NOT_BETWEEN: - if (childIndex == 0) { - out.append(" NOT BETWEEN "); - } else if (childIndex == 1) { - out.append(" AND "); - } - break; - case Expression.BITWISE_OR: - out.append(" ").append(operandForBitwiseOr()).append(" "); - break; - case Expression.BITWISE_AND: - out.append(" ").append(operandForBitwiseAnd()).append(" "); - break; - case Expression.BITWISE_XOR: - out.append(" ").append(operandForBitwiseXor()).append(" "); - break; - case Expression.BITWISE_LEFT_SHIFT: - out.append(" ").append(operandForBitwiseLeftShift()).append(" "); - break; - case Expression.BITWISE_RIGHT_SHIFT: - out.append(" ").append(operandForBitwiseRightShift()).append(""); - break; - } - } catch (IOException ioex) { - throw new CayenneRuntimeException("Error appending content", ioex); - } - - if (matchingObject) { - objectMatchTranslator.setOperation(out.toString()); - objectMatchTranslator.setExpression(node); - } - } - - /** - * @since 3.1 - */ - protected String operandForBitwiseNot() { - return "~"; - } - - /** - * @since 3.1 - */ - protected String operandForBitwiseOr() { - return "|"; - } - - /** - * @since 3.1 - */ - protected String operandForBitwiseAnd() { - return "&"; - } - - /** - * @since 3.1 - */ - protected String operandForBitwiseXor() { - return "^"; - } - - /** - * @since 4.0 - */ - protected String operandForBitwiseLeftShift() { - return "<<"; - } - - /** - * @since 4.0 - */ - protected String operandForBitwiseRightShift() { - return ">>"; - } - - @Override - public void startNode(Expression node, Expression parentNode) { - - if(waitingForEndNode != null) { - return; - } - - if(useAliasForExpressions) { - String alias = queryAssembler.getAliasForExpression(node); - if(alias != null) { - out.append(alias); - waitingForEndNode = node; - return; - } - } - - boolean parenthesisNeeded = parenthesisNeeded(node, parentNode); - - if(node.getType() == Expression.FUNCTION_CALL) { - if(node instanceof ASTExtract) { - appendExtractFunction((ASTExtract) node); - } else { - appendFunction((ASTFunctionCall) node); - } - if(parenthesisNeeded) { - out.append("("); - } - return; - } - - if(node.getType() == Expression.FULL_OBJECT && parentNode != null) { - throw new CayenneRuntimeException("Expression is not supported in where clause."); - } - - int count = node.getOperandCount(); - - if (count == 2) { - // binary nodes are the only ones that currently require this - detectObjectMatch(node); - } - - if (parenthesisNeeded) { - out.append('('); - } - - if (count == 0) { - // not all databases handle true/false - if (node.getType() == Expression.TRUE) { - out.append("1 = 1"); - } else if (node.getType() == Expression.FALSE) { - out.append("1 = 0"); - } else if (node.getType() == Expression.ASTERISK) { - out.append("*"); - } - } - - if (count == 1) { - if (node.getType() == Expression.NEGATIVE) { - out.append('-'); - } else if (node.getType() == Expression.NOT) { - out.append("NOT "); - } else if (node.getType() == Expression.BITWISE_NOT) { - out.append(operandForBitwiseNot()); - } - } else if ((node.getType() == Expression.LIKE_IGNORE_CASE || node.getType() == Expression.NOT_LIKE_IGNORE_CASE) - && !caseInsensitive) { - out.append("UPPER("); - } - - } - - /** - * @since 1.1 - */ - @Override - public void endNode(Expression node, Expression parentNode) { - - if(waitingForEndNode != null) { - if(node == waitingForEndNode) { - waitingForEndNode = null; - } - return; - } - - try { - // check if we need to use objectMatchTranslator to finish building the expression - if (node.getOperandCount() == 2 && matchingObject) { - appendObjectMatch(); - } - - boolean parenthesisNeeded = parenthesisNeeded(node, parentNode); - boolean likeIgnoreCase = (node.getType() == Expression.LIKE_IGNORE_CASE || node.getType() == Expression.NOT_LIKE_IGNORE_CASE); - boolean isPatternMatchNode = PatternMatchNode.class.isAssignableFrom(node.getClass()); - - // closing UPPER parenthesis - if (likeIgnoreCase && !caseInsensitive) { - out.append(')'); - } - - if (isPatternMatchNode) { - appendLikeEscapeCharacter((PatternMatchNode) node); - } - - // clean up trailing comma in function argument list - if(node.getType() == Expression.FUNCTION_CALL) { - clearLastFunctionArgDivider((ASTFunctionCall)node); - } - - // closing LIKE parenthesis - if (parenthesisNeeded) { - out.append(')'); - } - - // if inside function call, put comma between arguments - if(parentNode != null && parentNode.getType() == Expression.FUNCTION_CALL) { - appendFunctionArgDivider((ASTFunctionCall) parentNode); - } - } catch (IOException ioex) { - throw new CayenneRuntimeException("Error appending content", ioex); - } - } - - @Override - public void objectNode(Object leaf, Expression parentNode) { - if(waitingForEndNode != null) { - return; - } - - try { - switch (parentNode.getType()) { - case Expression.OBJ_PATH: - appendObjPath(parentNode); - break; - case Expression.DB_PATH: - appendDbPath(parentNode); - break; - case Expression.LIST: - appendList(parentNode, paramsDbType(parentNode)); - break; - case Expression.FUNCTION_CALL: - appendFunctionArg(leaf, (ASTFunctionCall)parentNode); - break; - default: - appendLiteral(leaf, paramsDbType(parentNode), parentNode); - } - } catch (IOException ioex) { - throw new CayenneRuntimeException("Error appending content", ioex); - } - } - - protected boolean parenthesisNeeded(Expression node, Expression parentNode) { - if (node.getType() == Expression.FUNCTION_CALL) { - return ((ASTFunctionCall)node).needParenthesis(); - } - - if (parentNode == null) { - return false; - } - - // only unary expressions can go w/o parenthesis - if (node.getOperandCount() > 1) { - return true; - } - - if (node.getType() == Expression.OBJ_PATH - || node.getType() == Expression.DB_PATH - || node.getType() == Expression.ASTERISK) { - return false; - } - - return true; - } - - private final void appendList(Expression listExpr, DbAttribute paramDesc) throws IOException { - Iterator<?> it; - Object list = listExpr.getOperand(0); - if (list instanceof List) { - it = ((List<?>) list).iterator(); - } else if (list instanceof Object[]) { - it = Arrays.asList((Object[]) list).iterator(); - } else { - String className = (list != null) ? list.getClass().getName() : "<null>"; - throw new IllegalArgumentException("Unsupported type for the list expressions: " + className); - } - - // process first element outside the loop - // (unroll loop to avoid condition checking - if (it.hasNext()) { - appendLiteral(it.next(), paramDesc, listExpr); - } else { - return; - } - - while (it.hasNext()) { - out.append(", "); - appendLiteral(it.next(), paramDesc, listExpr); - } - } - - @Override - protected void appendLiteral(Object val, DbAttribute attr, Expression parentExpression) throws IOException { - - if (!matchingObject) { - super.appendLiteral(val, attr, parentExpression); - } else if (val == null || (val instanceof Persistent)) { - objectMatchTranslator.setDataObject((Persistent) val); - } else if (val instanceof ObjectId) { - objectMatchTranslator.setObjectId((ObjectId) val); - } else { - throw new IllegalArgumentException("Attempt to use literal other than DataObject during object match."); - } - } - - @Override - protected void processRelTermination(DbRelationship rel, JoinType joinType, String joinSplitAlias) { - - if (!matchingObject) { - super.processRelTermination(rel, joinType, joinSplitAlias); - } else { - if (rel.isToMany()) { - // append joins - queryAssembler.dbRelationshipAdded(rel, joinType, joinSplitAlias); - } - objectMatchTranslator.setRelationship(rel, joinSplitAlias); - } - } - - /** - * Append function name to result SQL - * Override this method to rename or skip function if generic name isn't supported on target DB. - * @since 4.0 - */ - protected void appendFunction(ASTFunctionCall functionExpression) { - out.append(functionExpression.getFunctionName()); - } - - /** - * Special case for extract date/time parts functions as they have many variants - * @since 4.0 - */ - protected void appendExtractFunction(ASTExtract functionExpression) { - appendFunction(functionExpression); - } - - /** - * Append scalar argument of a function call - * Used only for values stored in ASTScalar other - * expressions appended in objectNode() method - * - * @since 4.0 - */ - protected void appendFunctionArg(Object value, ASTFunctionCall functionExpression) throws IOException { - // Create fake DbAttribute to pass argument info down to bind it to SQL prepared statement - DbAttribute dbAttrForArg = new DbAttribute(); - dbAttrForArg.setType(TypesMapping.getSqlTypeByJava(value.getClass())); - super.appendLiteral(value, dbAttrForArg, functionExpression); - appendFunctionArgDivider(functionExpression); - } - - /** - * Append divider between function arguments. - * In overriding methods can be replaced e.g. for " || " for CONCAT operation - * @since 4.0 - */ - protected void appendFunctionArgDivider(ASTFunctionCall functionExpression) { - out.append(", "); - } - - /** - * Clear last divider as we currently don't now position of argument until parent element is ended. - * @since 4.0 - */ - protected void clearLastFunctionArgDivider(ASTFunctionCall functionExpression) { - if(functionExpression.getOperandCount() > 0) { - out.delete(out.length() - 2, out.length()); - } - } - - /** - * Class to translate DB Entity qualifiers annotation to Obj-entity - * qualifiers annotation This is done by changing all Obj-paths to Db-paths - * and rejecting all original Db-paths - */ - class DbEntityQualifierTransformer implements Function<Object, Object> { - - public Object apply(Object input) { - if (input instanceof ASTObjPath) { - return new ASTDbPath(((SimpleNode) input).getOperand(0)); - } - return input; - } - } +class QualifierTranslator implements TraversalHandler { + + private final TranslatorContext context; + private final PathTranslator pathTranslator; + private final Set<Object> expressionsToSkip; + private final Deque<Node> nodeStack; + + private Node currentNode; + + QualifierTranslator(TranslatorContext context) { + this.context = context; + this.pathTranslator = context.getPathTranslator(); + this.expressionsToSkip = new HashSet<>(); + this.nodeStack = new ArrayDeque<>(); + } + + Node translate(BaseProperty<?> property) { + if(property == null) { + return null; + } + + Node result = translate(property.getExpression()); + if(property.getAlias() != null) { + return aliased(result, property.getAlias()).build(); + } + return result; + } + + Node translate(Expression qualifier) { + if(qualifier == null) { + return null; + } + + Node rootNode = new EmptyNode(); + expressionsToSkip.clear(); + boolean hasCurrentNode = currentNode != null; + if(hasCurrentNode) { + nodeStack.push(currentNode); + } + + currentNode = rootNode; + qualifier.traverse(this); + + if(hasCurrentNode) { + currentNode = nodeStack.pop(); + } else { + currentNode = null; + } + + if(rootNode.getChildrenCount() == 1) { + // trim empty node + Node child = rootNode.getChild(0); + child.setParent(null); + return child; + } + return rootNode; + } + + @Override + public void startNode(Expression node, Expression parentNode) { + if(expressionsToSkip.contains(node) || expressionsToSkip.contains(parentNode)) { + return; + } + Node nextNode = expressionNodeToSqlNode(node, parentNode); + if(nextNode == null) { + return; + } + currentNode.addChild(nextNode); + nextNode.setParent(currentNode); + currentNode = nextNode; + } + + private Node expressionNodeToSqlNode(Expression node, Expression parentNode) { + switch (node.getType()) { + case NOT_IN: + return new InNode(true); + case IN: + return new InNode(false); + case NOT_BETWEEN: + case BETWEEN: + return new BetweenNode(node.getType() == NOT_BETWEEN); + case NOT: + return new NotNode(); + case BITWISE_NOT: + return new BitwiseNotNode(); + case EQUAL_TO: + return new EqualNode(); + case NOT_EQUAL_TO: + return new NotEqualNode(); + + case LIKE: + case NOT_LIKE: + case LIKE_IGNORE_CASE: + case NOT_LIKE_IGNORE_CASE: + PatternMatchNode patternMatchNode = (PatternMatchNode)node; + boolean not = node.getType() == NOT_LIKE || node.getType() == NOT_LIKE_IGNORE_CASE; + return new LikeNode(patternMatchNode.isIgnoringCase(), not, patternMatchNode.getEscapeChar()); + + case OBJ_PATH: + String path = (String)node.getOperand(0); + PathTranslationResult result = pathTranslator.translatePath(context.getMetadata().getObjEntity(), path); + return processPathTranslationResult(node, parentNode, result); + + case DB_PATH: + String dbPath = (String)node.getOperand(0); + PathTranslationResult dbResult = pathTranslator.translatePath(context.getMetadata().getDbEntity(), dbPath); + return processPathTranslationResult(node, parentNode, dbResult); + + case FUNCTION_CALL: + ASTFunctionCall functionCall = (ASTFunctionCall)node; + return function(functionCall.getFunctionName()).build(); + + case ADD: + case SUBTRACT: + case MULTIPLY: + case DIVIDE: + case NEGATIVE: + case BITWISE_AND: + case BITWISE_LEFT_SHIFT: + case BITWISE_OR: + case BITWISE_RIGHT_SHIFT: + case BITWISE_XOR: + case OR: + case AND: + case LESS_THAN: + case LESS_THAN_EQUAL_TO: + case GREATER_THAN: + case GREATER_THAN_EQUAL_TO: + return new OpExpressionNode(expToStr(node.getType())); + + case TRUE: + case FALSE: + case ASTERISK: + return new TextNode(' ' + expToStr(node.getType())); + + case EXISTS: + return new FunctionNode("EXISTS", null, false); + + case SUBQUERY: + ASTSubquery subquery = (ASTSubquery)node; + DefaultSelectTranslator translator = new DefaultSelectTranslator(subquery.getQuery(), context); + translator.translate(); + return translator.getContext().getSelectBuilder().build(); + + case ENCLOSING_OBJECT: + // Translate via parent context's translator + Expression expression = (Expression) node.getOperand(0); + if(context.getParentContext() == null) { + throw new CayenneRuntimeException("Unable to translate qualifier, no parent context to use for expression " + node); + } + expressionsToSkip.add(expression); + return context.getParentContext().getQualifierTranslator().translate(expression); + + case FULL_OBJECT: + ASTFullObject fullObject = (ASTFullObject)node; + if(fullObject.getOperandCount() == 0) { + Collection<DbAttribute> dbAttributes = context.getMetadata().getDbEntity().getPrimaryKeys(); + if(dbAttributes.size() > 1) { + throw new CayenneRuntimeException("Unable to translate reference on entity with more than one PK."); + } + DbAttribute attribute = dbAttributes.iterator().next(); + String alias = context.getTableTree().aliasForAttributePath(attribute.getName()); + return table(alias).column(attribute).build(); + } else { + return null; + } + } + + return null; + } + + private Node processPathTranslationResult(Expression node, Expression parentNode, PathTranslationResult result) { + if(result.getDbRelationship().isPresent() + && result.getDbAttributes().size() > 1 + && result.getDbRelationship().get().getTargetEntity().getPrimaryKeys().size() > 1) { + return createMultiAttributeMatch(node, parentNode, result); + } else if(result.getDbAttributes().isEmpty()) { + return new EmptyNode(); + } else { + String alias = context.getTableTree().aliasForPath(result.getLastAttributePath()); + return table(alias).column(result.getLastAttribute()).build(); + } + } + + private Node createMultiAttributeMatch(Expression node, Expression parentNode, PathTranslationResult result) { + ObjectId objectId = null; + expressionsToSkip.add(node); + expressionsToSkip.add(parentNode); + + int siblings = parentNode.getOperandCount(); + for(int i=0; i<siblings; i++) { + Object operand = parentNode.getOperand(i); + if(node == operand) { + continue; + } + + if(operand instanceof Persistent) { + objectId = ((Persistent) operand).getObjectId(); + break; + } else if(operand instanceof ObjectId) { + objectId = (ObjectId)operand; + break; + } else if(operand instanceof ASTObjPath) { + // TODO: support comparision of multi attribute ObjPath with other multi attribute ObjPath + throw new UnsupportedOperationException("Comparision of multiple attributes not supported for ObjPath"); + } + } + + if(objectId == null) { + throw new CayenneRuntimeException("Multi attribute ObjPath isn't matched with valid value. " + + "List or Persistent object required."); + } + + ExpressionNodeBuilder expressionNodeBuilder = null; + ExpressionNodeBuilder eq; + + Iterator<DbAttribute> pkIt = result.getDbRelationship().get().getTargetEntity().getPrimaryKeys().iterator(); + int count = result.getDbAttributes().size(); + + for(int i=0; i<count; i++) { + String path = result.getAttributePaths().get(i); + DbAttribute attribute = result.getDbAttributes().get(i); + DbAttribute pk = pkIt.next(); + String alias = context.getTableTree().aliasForPath(path); + Object nextValue = objectId.getIdSnapshot().get(pk.getName()); + eq = table(alias).column(attribute).eq(value(nextValue)); + if (expressionNodeBuilder == null) { + expressionNodeBuilder = eq; + } else { + expressionNodeBuilder = expressionNodeBuilder.and(eq); + } + } + + return expressionNodeBuilder.build(); + } + + @Override + public void endNode(Expression node, Expression parentNode) { + if(expressionsToSkip.contains(node) || expressionsToSkip.contains(parentNode)) { + return; + } + if(currentNode.getParent() != null) { + currentNode = currentNode.getParent(); + } + } + + @Override + public void objectNode(Object leaf, Expression parentNode) { + if(expressionsToSkip.contains(parentNode)) { + return; + } + if(parentNode.getType() == Expression.OBJ_PATH || parentNode.getType() == Expression.DB_PATH) { + return; + } + + ValueNodeBuilder valueNodeBuilder = value(leaf).attribute(findDbAttribute(parentNode)); + if(parentNode.getType() == Expression.LIST) { + valueNodeBuilder.array(true); + } + Node nextNode = valueNodeBuilder.build(); + + currentNode.addChild(nextNode); + nextNode.setParent(currentNode); + } + + protected DbAttribute findDbAttribute(Expression node) { + int len = node.getOperandCount(); + if (len != 2) { + if (node instanceof SimpleNode) { + Expression parent = (Expression) ((SimpleNode) node).jjtGetParent(); + if (parent != null) { + node = parent; + } else { + return null; + } + } + } + + PathTranslationResult result = null; + for(int i=0; i<node.getOperandCount(); i++) { + Object op = node.getOperand(i); + if(op instanceof ASTObjPath) { + result = pathTranslator.translatePath(context.getMetadata().getObjEntity(), ((ASTObjPath) op).getPath()); + break; + } else if(op instanceof ASTDbPath) { + result = pathTranslator.translatePath(context.getMetadata().getDbEntity(), ((ASTDbPath) op).getPath()); + break; + } + } + + if(result == null) { + return null; + } + + return result.getLastAttribute(); + } + + @Override + public void finishedChild(Expression node, int childIndex, boolean hasMoreChildren) { + } + + private String expToStr(int type) { + switch (type) { + case AND: + return "AND"; + case OR: + return "OR"; + case LESS_THAN: + return "<"; + case LESS_THAN_EQUAL_TO: + return "<="; + case GREATER_THAN: + return ">"; + case GREATER_THAN_EQUAL_TO: + return ">="; + case ADD: + return "+"; + case SUBTRACT: + return "-"; + case MULTIPLY: + return "*"; + case DIVIDE: + return "/"; + case NEGATIVE: + return "-"; + case BITWISE_AND: + return "&"; + case BITWISE_OR: + return "|"; + case BITWISE_XOR: + return "^"; + case BITWISE_NOT: + return "!"; + case BITWISE_LEFT_SHIFT: + return "<<"; + case BITWISE_RIGHT_SHIFT: + return ">>"; + case TRUE: + return "1=1"; + case FALSE: + return "1=0"; + case ASTERISK: + return "*"; + default: + return "{other}"; + } + } + } http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssembler.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssembler.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssembler.java deleted file mode 100644 index 3293168..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssembler.java +++ /dev/null @@ -1,197 +0,0 @@ -/***************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * <p/> - * http://www.apache.org/licenses/LICENSE-2.0 - * <p/> - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - ****************************************************************/ - -package org.apache.cayenne.access.translator.select; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.apache.cayenne.access.translator.DbAttributeBinding; -import org.apache.cayenne.access.types.ExtendedType; -import org.apache.cayenne.dba.DbAdapter; -import org.apache.cayenne.exp.Expression; -import org.apache.cayenne.map.DbAttribute; -import org.apache.cayenne.map.DbRelationship; -import org.apache.cayenne.map.EntityResolver; -import org.apache.cayenne.map.JoinType; -import org.apache.cayenne.query.Query; -import org.apache.cayenne.query.QueryMetadata; - -/** - * Abstract superclass of Query translators. - */ -public abstract class QueryAssembler { - - protected Query query; - protected QueryMetadata queryMetadata; - protected boolean translated; - protected String sql; - protected DbAdapter adapter; - protected EntityResolver entityResolver; - protected List<DbAttributeBinding> bindings; - /** - * @since 4.0 - */ - protected AddBindingListener addBindingListener; - - /** - * @since 4.0 - */ - public QueryAssembler(Query query, DbAdapter adapter, EntityResolver entityResolver) { - this.entityResolver = entityResolver; - this.adapter = adapter; - this.query = query; - this.queryMetadata = query.getMetaData(entityResolver); - this.bindings = new ArrayList<>(); - } - - /** - * Returns aliases for the path splits defined in the query. - * - * @since 3.0 - */ - protected Map<String, String> getPathAliases() { - return queryMetadata.getPathSplitAliases(); - } - - public EntityResolver getEntityResolver() { - return entityResolver; - } - - public DbAdapter getAdapter() { - return adapter; - } - - /** - * Returns query object being processed. - */ - public Query getQuery() { - return query; - } - - public QueryMetadata getQueryMetadata() { - return queryMetadata; - } - - /** - * A callback invoked by a child qualifier or ordering processor allowing - * query assembler to reset its join stack. - * - * @since 3.0 - */ - public abstract void resetJoinStack(); - - /** - * Returns an alias of the table which is currently at the top of the join - * stack. - * - * @since 3.0 - */ - public abstract String getCurrentAlias(); - - /** - * Appends a join with given semantics to the query. - * - * @since 3.0 - */ - public abstract void dbRelationshipAdded(DbRelationship relationship, JoinType joinType, String joinSplitAlias); - - /** - * Translates query into an SQL string formatted to use in a - * PreparedStatement. - */ - public String getSql() { - ensureTranslated(); - return sql; - } - - /** - * @since 4.0 - */ - protected void ensureTranslated() { - if (!translated) { - doTranslate(); - translated = true; - } - } - - /** - * @since 4.0 - */ - protected abstract void doTranslate(); - - /** - * Returns <code>true</code> if table aliases are supported. Default - * implementation returns false. - */ - public boolean supportsTableAliases() { - return false; - } - - /** - * Registers <code>anObject</code> as a PreparedStatement parameter. - * - * @param anObject - * object that represents a value of DbAttribute - * @param dbAttr - * DbAttribute being processed. - */ - public void addToParamList(DbAttribute dbAttr, Object anObject) { - ExtendedType extendedType = anObject != null - ? adapter.getExtendedTypes().getRegisteredType(anObject.getClass()) - : adapter.getExtendedTypes().getDefaultType(); - - DbAttributeBinding binding = new DbAttributeBinding(dbAttr); - binding.setStatementPosition(bindings.size() + 1); - binding.setValue(anObject); - binding.setExtendedType(extendedType); - - bindings.add(binding); - if(addBindingListener != null) { - addBindingListener.onAdd(binding); - } - } - - /** - * @since 4.0 - */ - public DbAttributeBinding[] getBindings() { - return bindings.toArray(new DbAttributeBinding[bindings.size()]); - } - - /** - * @since 4.0 - */ - public abstract String getAliasForExpression(Expression exp); - - /** - * @since 4.0 - */ - public void setAddBindingListener(AddBindingListener addBindingListener) { - this.addBindingListener = addBindingListener; - } - - /** - * @since 4.0 - */ - protected interface AddBindingListener { - void onAdd(DbAttributeBinding binding); - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssemblerHelper.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssemblerHelper.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssemblerHelper.java deleted file mode 100644 index fcdad65..0000000 --- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/QueryAssemblerHelper.java +++ /dev/null @@ -1,488 +0,0 @@ -/***************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - ****************************************************************/ - -package org.apache.cayenne.access.translator.select; - -import org.apache.cayenne.CayenneRuntimeException; -import org.apache.cayenne.ObjectId; -import org.apache.cayenne.Persistent; -import org.apache.cayenne.dba.QuotingStrategy; -import org.apache.cayenne.exp.Expression; -import org.apache.cayenne.exp.parser.PatternMatchNode; -import org.apache.cayenne.exp.parser.SimpleNode; -import org.apache.cayenne.map.*; -import org.apache.cayenne.util.CayenneMapEntry; - -import java.io.IOException; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -/** - * Translates parts of the query to SQL. Always works in the context of parent - * Translator. - */ -public abstract class QueryAssemblerHelper { - - protected QueryAssembler queryAssembler; - protected StringBuilder out; - protected QuotingStrategy strategy; - - /** - * Force joining tables for all relations, not only for toMany - * @since 4.0 - */ - private boolean forceJoinForRelations; - - /** - * Creates QueryAssemblerHelper initializing with parent - * {@link QueryAssembler} and output buffer object. - */ - public QueryAssemblerHelper(QueryAssembler queryAssembler) { - this.queryAssembler = queryAssembler; - strategy = queryAssembler.getAdapter().getQuotingStrategy(); - } - - public ObjEntity getObjEntity() { - return queryAssembler.getQueryMetadata().getObjEntity(); - } - - public DbEntity getDbEntity() { - return queryAssembler.getQueryMetadata().getDbEntity(); - } - - /** - * @since 3.0 - */ - public StringBuilder appendPart(StringBuilder out) { - this.out = out; - doAppendPart(); - return out; - } - - /** - * Sets ouput buffer - */ - void setOut(StringBuilder out) { - this.out = out; - } - - /** - * @return output buffer - */ - StringBuilder getOut() { - return out; - } - - /** - * @since 3.0 - */ - protected abstract void doAppendPart(); - - /** - * <p> - * Outputs the standard JDBC (database agnostic) expression for supplying - * the escape character to the database server when supplying a LIKE clause. - * This has been factored-out because some database adaptors handle LIKE - * differently and they need access to this common method in order not to - * repeat this code. - * <p> - * If there is no escape character defined then this method will not output - * anything. An escape character of 0 will mean no escape character. - * - * @since 3.1 - */ - protected void appendLikeEscapeCharacter(PatternMatchNode patternMatchNode) throws IOException { - char escapeChar = patternMatchNode.getEscapeChar(); - - if ('?' == escapeChar) { - throw new CayenneRuntimeException("the escape character of '?' is illegal for LIKE clauses."); - } - - if (0 != escapeChar) { - out.append(" {escape '"); - out.append(escapeChar); - out.append("'}"); - } - } - - /** - * Processes parts of the OBJ_PATH expression. - */ - protected void appendObjPath(Expression pathExp) { - - queryAssembler.resetJoinStack(); - String joinSplitAlias = null; - - for (PathComponent<ObjAttribute, ObjRelationship> component : getObjEntity().resolvePath(pathExp, - queryAssembler.getPathAliases())) { - - if (component.isAlias()) { - joinSplitAlias = component.getName(); - for (PathComponent<ObjAttribute, ObjRelationship> aliasPart : component.getAliasedPath()) { - - ObjRelationship relationship = aliasPart.getRelationship(); - - if (relationship == null) { - throw new IllegalStateException("Non-relationship aliased path part: " + aliasPart.getName()); - } - - if (aliasPart.isLast() && component.isLast()) { - processRelTermination(relationship, aliasPart.getJoinType(), joinSplitAlias); - } else { - // find and add joins .... - for (DbRelationship dbRel : relationship.getDbRelationships()) { - queryAssembler.dbRelationshipAdded(dbRel, aliasPart.getJoinType(), joinSplitAlias); - } - } - } - - continue; - } - - ObjRelationship relationship = component.getRelationship(); - ObjAttribute attribute = component.getAttribute(); - - if (relationship != null) { - - // if this is a last relationship in the path, - // it needs special handling - if (component.isLast()) { - processRelTermination(relationship, component.getJoinType(), joinSplitAlias); - } else { - // find and add joins .... - for (DbRelationship dbRel : relationship.getDbRelationships()) { - queryAssembler.dbRelationshipAdded(dbRel, component.getJoinType(), joinSplitAlias); - } - } - } else { - Iterator<CayenneMapEntry> dbPathIterator = attribute.getDbPathIterator(); - while (dbPathIterator.hasNext()) { - Object pathPart = dbPathIterator.next(); - - if (pathPart == null) { - throw new CayenneRuntimeException("ObjAttribute has no component: %s", attribute.getName()); - } else if (pathPart instanceof DbRelationship) { - queryAssembler.dbRelationshipAdded((DbRelationship) pathPart, JoinType.INNER, joinSplitAlias); - } else if (pathPart instanceof DbAttribute) { - processColumnWithQuoteSqlIdentifiers((DbAttribute) pathPart, pathExp); - } - } - - } - } - } - - protected void appendDbPath(Expression pathExp) { - - queryAssembler.resetJoinStack(); - String joinSplitAlias = null; - - for (PathComponent<DbAttribute, DbRelationship> component : getDbEntity().resolvePath(pathExp, - queryAssembler.getPathAliases())) { - - if (component.isAlias()) { - joinSplitAlias = component.getName(); - for (PathComponent<DbAttribute, DbRelationship> aliasPart : component.getAliasedPath()) { - - DbRelationship relationship = aliasPart.getRelationship(); - - if (relationship == null) { - throw new IllegalStateException("Non-relationship aliased path part: " + aliasPart.getName()); - } - - if (aliasPart.isLast() && component.isLast()) { - processRelTermination(relationship, aliasPart.getJoinType(), joinSplitAlias); - } else { - queryAssembler.dbRelationshipAdded(relationship, component.getJoinType(), joinSplitAlias); - } - } - - continue; - } - - DbRelationship relationship = component.getRelationship(); - - if (relationship != null) { - - // if this is a last relationship in the path, - // it needs special handling - if (component.isLast()) { - processRelTermination(relationship, component.getJoinType(), joinSplitAlias); - } else { - // find and add joins .... - queryAssembler.dbRelationshipAdded(relationship, component.getJoinType(), joinSplitAlias); - } - } else { - processColumnWithQuoteSqlIdentifiers(component.getAttribute(), pathExp); - } - } - } - - protected void processColumn(DbAttribute dbAttr) { - processColumnWithQuoteSqlIdentifiers(dbAttr, null); - } - - protected void processColumnWithQuoteSqlIdentifiers(DbAttribute dbAttr, Expression pathExp) { - - String alias = (queryAssembler.supportsTableAliases()) ? queryAssembler.getCurrentAlias() : null; - out.append(strategy.quotedIdentifier(dbAttr.getEntity(), alias, dbAttr.getName())); - } - - /** - * 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 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(Object val, DbAttribute attr, Expression parentExpression) throws IOException { - - if (val == null) { - out.append("NULL"); - } else if (val instanceof Persistent) { - // TODO: see cay1796 - // This check is unlikely to happen, - // since Expression got ObjectId from Persistent object. - // Left for future research. - ObjectId id = ((Persistent) 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<String, Object> snap = id.getIdSnapshot(); - if (snap.size() != 1) { - throw new CayenneRuntimeException("Object must have a single primary key column to serve " + - "as a query parameter. This object has %s: %s", snap.size(), snap); - } - - // checks have been passed, use id value - appendLiteralDirect(snap.get(snap.keySet().iterator().next()), attr, parentExpression); - } else if (val instanceof ObjectId) { - - ObjectId id = (ObjectId) val; - - if (id.isTemporary()) { - throw new CayenneRuntimeException("Can't use NEW object as a query parameter."); - } - - Map<String, Object> snap = id.getIdSnapshot(); - if (snap.size() != 1) { - throw new CayenneRuntimeException("Object must have a single primary key column to serve " + - "as a query parameter. This object has %s: %s", snap.size(), snap); - } - - // checks have been passed, use id value - appendLiteralDirect(snap.get(snap.keySet().iterator().next()), attr, parentExpression); - } else { - appendLiteralDirect(val, attr, parentExpression); - } - } - - /** - * 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. - */ - protected void appendLiteralDirect(Object val, DbAttribute attr, Expression parentExpression) throws IOException { - out.append('?'); - 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(); - - // for unary expressions, find parent binary - this is a hack mainly to - // support - // ASTList - if (len < 2) { - - if (e instanceof SimpleNode) { - Expression parent = (Expression) ((SimpleNode) e).jjtGetParent(); - if (parent != null) { - return paramsDbType(parent); - } - } - - return null; - } - - // naive algorithm: - - // if at least one of the sibling operands is a - // OBJ_PATH or DB_PATH expression, use its attribute type as - // a final answer. - - // find attribute or relationship matching the value - DbAttribute attribute = null; - DbRelationship relationship = null; - for (int i = 0; i < len; i++) { - Object op = e.getOperand(i); - - if (op instanceof Expression) { - Expression expression = (Expression) op; - if (expression.getType() == Expression.OBJ_PATH) { - PathComponent<ObjAttribute, ObjRelationship> last = getObjEntity().lastPathComponent(expression, - queryAssembler.getPathAliases()); - - // TODO: handle EmbeddableAttribute - // if (last instanceof EmbeddableAttribute) - // break; - - if (last.getAttribute() != null) { - attribute = last.getAttribute().getDbAttribute(); - break; - } else if (last.getRelationship() != null) { - List<DbRelationship> dbPath = last.getRelationship().getDbRelationships(); - if (dbPath.size() > 0) { - relationship = dbPath.get(dbPath.size() - 1); - break; - } - } - } else if (expression.getType() == Expression.DB_PATH) { - PathComponent<DbAttribute, DbRelationship> last = getDbEntity().lastPathComponent(expression, - queryAssembler.getPathAliases()); - if (last.getAttribute() != null) { - attribute = last.getAttribute(); - break; - } else if (last.getRelationship() != null) { - relationship = last.getRelationship(); - break; - } - } - } - } - - if (attribute != null) { - return attribute; - } - - if (relationship != null) { - // Can't properly handle multiple joins.... - if (relationship.getJoins().size() == 1) { - DbJoin join = relationship.getJoins().get(0); - return join.getSource(); - } - } - - return null; - } - - /** - * 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 expression for the source foreign key is added. - * - * @since 3.0 - */ - protected void processRelTermination(ObjRelationship rel, JoinType joinType, String joinSplitAlias) { - - Iterator<DbRelationship> dbRels = rel.getDbRelationships().iterator(); - - // scan DbRelationships - while (dbRels.hasNext()) { - DbRelationship dbRel = dbRels.next(); - - // if this is a last relationship in the path, - // it needs special handling - if (!dbRels.hasNext()) { - processRelTermination(dbRel, joinType, joinSplitAlias); - } else { - // find and add joins .... - queryAssembler.dbRelationshipAdded(dbRel, joinType, joinSplitAlias); - } - } - } - - /** - * 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 - * expression for the source foreign key is added. - * - * @since 3.0 - */ - protected void processRelTermination(DbRelationship rel, JoinType joinType, String joinSplitAlias) { - - if (forceJoinForRelations || rel.isToMany()) { - // append joins - queryAssembler.dbRelationshipAdded(rel, joinType, joinSplitAlias); - } - - // get last DbRelationship on the list - List<DbJoin> joins = rel.getJoins(); - if (joins.size() != 1) { - String msg = "OBJ_PATH expressions are only supported for a single-join relationships. " + - "This relationship has %s joins."; - throw new CayenneRuntimeException(msg, joins.size()); - } - - DbJoin join = joins.get(0); - - DbAttribute attribute; - - if (rel.isToMany()) { - DbEntity ent = join.getRelationship().getTargetEntity(); - Collection<DbAttribute> pk = ent.getPrimaryKeys(); - if (pk.size() != 1) { - String msg = "DB_NAME expressions can only support targets with a single column PK. " + - "This entity has %d columns in primary key."; - throw new CayenneRuntimeException(msg, pk.size()); - } - - attribute = pk.iterator().next(); - } else { - attribute = forceJoinForRelations ? join.getTarget() : join.getSource(); - } - - processColumn(attribute); - } - - /** - * Force joining tables for all relations, not only for toMany - * @since 4.0 - */ - protected void setForceJoinForRelations(boolean forceJoinForRelations) { - this.forceJoinForRelations = forceJoinForRelations; - } -} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ResultNodeDescriptor.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ResultNodeDescriptor.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ResultNodeDescriptor.java new file mode 100644 index 0000000..1488c6a --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/ResultNodeDescriptor.java @@ -0,0 +1,138 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.access.translator.select; + +import org.apache.cayenne.access.sqlbuilder.sqltree.ColumnNode; +import org.apache.cayenne.access.sqlbuilder.sqltree.Node; +import org.apache.cayenne.access.sqlbuilder.sqltree.NodeType; +import org.apache.cayenne.access.sqlbuilder.sqltree.SimpleNodeTreeVisitor; +import org.apache.cayenne.dba.TypesMapping; +import org.apache.cayenne.exp.parser.ASTAggregateFunctionCall; +import org.apache.cayenne.exp.property.BaseProperty; +import org.apache.cayenne.map.DbAttribute; + +/** + * @since 4.2 + */ +class ResultNodeDescriptor { + private final Node node; + private final boolean inDataRow; + private final boolean isAggregate; + private final BaseProperty<?> property; + + private String dataRowKey; + private DbAttribute dbAttribute; + private String javaType; + + ResultNodeDescriptor(Node node, boolean inDataRow, BaseProperty<?> property, String dataRowKey) { + this.node = node; + this.inDataRow = inDataRow; + this.property = property; + this.dataRowKey = dataRowKey; + this.isAggregate = property != null + && property.getExpression() instanceof ASTAggregateFunctionCall; + } + + public boolean isAggregate() { + return isAggregate; + } + + public boolean isInDataRow() { + return inDataRow; + } + + public BaseProperty<?> getProperty() { + return property; + } + + public Node getNode() { + return node; + } + + public String getDataRowKey() { + if (dataRowKey != null) { + return dataRowKey; + } + if (property != null) { + return property.getAlias(); + } + if (getDbAttribute() != null) { + return getDbAttribute().getName(); + } + return null; + } + + public void setDataRowKey(String dataRowKey) { + this.dataRowKey = dataRowKey; + } + + public ResultNodeDescriptor setJavaType(String javaType) { + this.javaType = javaType; + return this; + } + + public ResultNodeDescriptor setDbAttribute(DbAttribute dbAttribute) { + this.dbAttribute = dbAttribute; + return this; + } + + public String getJavaType() { + if (javaType != null) { + return javaType; + } + if (property != null) { + return property.getType().getCanonicalName(); + } + if (getDbAttribute() != null) { + return TypesMapping.getJavaBySqlType(getDbAttribute().getType()); + } + return null; + } + + public int getJdbcType() { + if (getDbAttribute() != null) { + return getDbAttribute().getType(); + } + + if (getProperty() != null) { + return TypesMapping.getSqlTypeByJava(getProperty().getType()); + } + + return TypesMapping.NOT_DEFINED; + } + + public DbAttribute getDbAttribute() { + if (this.dbAttribute != null) { + return this.dbAttribute; + } + DbAttribute[] dbAttribute = {null}; + node.visit(new SimpleNodeTreeVisitor() { + @Override + public boolean onNodeStart(Node node) { + if (node.getType() == NodeType.COLUMN) { + dbAttribute[0] = ((ColumnNode) node).getAttribute(); + return false; + } + return true; + } + }); + return this.dbAttribute = dbAttribute[0]; + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SQLGenerationStage.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SQLGenerationStage.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SQLGenerationStage.java new file mode 100644 index 0000000..9551035 --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SQLGenerationStage.java @@ -0,0 +1,44 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.access.translator.select; + +import org.apache.cayenne.access.sqlbuilder.SQLGenerationVisitor; +import org.apache.cayenne.access.sqlbuilder.sqltree.Node; + +/** + * @since 4.2 + */ +class SQLGenerationStage implements TranslationStage { + + @Override + public void perform(TranslatorContext context) { + if(context.isSkipSQLGeneration()) { + return; + } + // Build final SQL tree + Node node = context.getSelectBuilder().build(); + // convert to database flavour + node = context.getAdapter().getSqlTreeProcessor().apply(node); + // generate SQL + SQLGenerationVisitor visitor = new SQLGenerationVisitor(new DefaultQuotingAppendable(context)); + node.visit(visitor); + context.setFinalSQL(visitor.getSQLString()); + } +} http://git-wip-us.apache.org/repos/asf/cayenne/blob/f6b2dac9/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SelectQueryWrapper.java ---------------------------------------------------------------------- diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SelectQueryWrapper.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SelectQueryWrapper.java new file mode 100644 index 0000000..576bb8d --- /dev/null +++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/SelectQueryWrapper.java @@ -0,0 +1,84 @@ +/***************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ****************************************************************/ + +package org.apache.cayenne.access.translator.select; + +import java.util.Collection; +import java.util.Objects; + +import org.apache.cayenne.exp.Expression; +import org.apache.cayenne.exp.property.BaseProperty; +import org.apache.cayenne.map.EntityResolver; +import org.apache.cayenne.query.Ordering; +import org.apache.cayenne.query.PrefetchTreeNode; +import org.apache.cayenne.query.QueryMetadata; +import org.apache.cayenne.query.Select; +import org.apache.cayenne.query.SelectQuery; + +/** + * @since 4.2 + */ +public class SelectQueryWrapper implements TranslatableQueryWrapper { + + private final SelectQuery<?> selectQuery; + + public SelectQueryWrapper(SelectQuery<?> selectQuery) { + this.selectQuery = Objects.requireNonNull(selectQuery); + } + + @Override + public boolean isDistinct() { + return selectQuery.isDistinct(); + } + + @Override + public QueryMetadata getMetaData(EntityResolver resolver) { + return selectQuery.getMetaData(resolver); + } + + @Override + public PrefetchTreeNode getPrefetchTree() { + return selectQuery.getPrefetchTree(); + } + + @Override + public Expression getQualifier() { + return selectQuery.getQualifier(); + } + + @Override + public Collection<Ordering> getOrderings() { + return selectQuery.getOrderings(); + } + + @Override + public Collection<BaseProperty<?>> getColumns() { + return selectQuery.getColumns(); + } + + @Override + public Expression getHavingQualifier() { + return selectQuery.getHavingQualifier(); + } + + @Override + public Select<?> unwrap() { + return selectQuery; + } +}