You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2015/04/28 12:45:10 UTC
[5/7] cayenne git commit: Refactoring SelectTranslator for better
extensibility
http://git-wip-us.apache.org/repos/asf/cayenne/blob/81f4fb50/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/OrderingTranslator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/OrderingTranslator.java b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/OrderingTranslator.java
index b875374..4a16678 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/OrderingTranslator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/access/translator/select/OrderingTranslator.java
@@ -19,7 +19,6 @@
package org.apache.cayenne.access.translator.select;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -35,88 +34,85 @@ import org.apache.cayenne.query.SelectQuery;
*/
public class OrderingTranslator extends QueryAssemblerHelper {
- protected List<String> orderByColumnList = new ArrayList<String>();
-
- public OrderingTranslator(QueryAssembler queryAssembler) {
- super(queryAssembler);
- }
-
- /**
- * Translates query Ordering list to SQL ORDER BY clause. Ordering list is obtained
- * from <code>queryAssembler</code>'s query object. In a process of building of
- * ORDER BY clause, <code>queryAssembler</code> is notified when a join needs to be
- * added.
- *
- * @since 3.0
- */
- @Override
- protected void doAppendPart() throws IOException {
-
- Query q = queryAssembler.getQuery();
-
- // only select queries can have ordering...
- if (q == null || !(q instanceof SelectQuery)) {
- return;
- }
-
- Iterator<Ordering> it = ((SelectQuery<?>) q).getOrderings().iterator();
-
- Appendable mainBuffer = this.out;
-
- try {
- while (it.hasNext()) {
- Ordering ord = it.next();
-
- // reset buffer to collect SQL for the single column, that we'll be reusing
- this.out = new StringBuilder();
-
- if (ord.isCaseInsensitive()) {
- out.append("UPPER(");
- }
-
- Expression exp = ord.getSortSpec();
-
- if (exp.getType() == Expression.OBJ_PATH) {
- appendObjPath(exp);
- }
- else if (exp.getType() == Expression.DB_PATH) {
- appendDbPath(exp);
- }
- else {
- throw new CayenneRuntimeException("Unsupported ordering expression: "
- + exp);
- }
-
- // Close UPPER() modifier
- if (ord.isCaseInsensitive()) {
- out.append(")");
- }
-
- String columnSQL = out.toString();
- mainBuffer.append(columnSQL);
- orderByColumnList.add(columnSQL);
-
- // "ASC" is a noop, omit it from the query
- if (!ord.isAscending()) {
- mainBuffer.append(" DESC");
- }
-
- if (it.hasNext()) {
- mainBuffer.append(", ");
- }
- }
- }
- finally {
- this.out = mainBuffer;
- }
- }
-
- /**
- * Returns the column expressions (not Expressions) used in the order by clause. E.g.,
- * in the case of an case-insensitive order by, an element of the list would be
- * <code>UPPER(<column reference>)</code>
- */
- public List<String> getOrderByColumnList() {
- return orderByColumnList;
- }
+ protected List<String> orderByColumnList = new ArrayList<String>();
+
+ public OrderingTranslator(QueryAssembler queryAssembler) {
+ super(queryAssembler);
+ }
+
+ /**
+ * Translates query Ordering list to SQL ORDER BY clause. Ordering list is
+ * obtained from <code>queryAssembler</code>'s query object. In a process of
+ * building of ORDER BY clause, <code>queryAssembler</code> is notified when
+ * a join needs to be added.
+ *
+ * @since 3.0
+ */
+ @Override
+ protected void doAppendPart() {
+
+ Query q = queryAssembler.getQuery();
+
+ // only select queries can have ordering...
+ if (q == null || !(q instanceof SelectQuery)) {
+ return;
+ }
+
+ Iterator<Ordering> it = ((SelectQuery<?>) q).getOrderings().iterator();
+
+ StringBuilder mainBuffer = this.out;
+
+ try {
+ while (it.hasNext()) {
+ Ordering ord = it.next();
+
+ // reset buffer to collect SQL for the single column, that we'll
+ // be reusing
+ this.out = new StringBuilder();
+
+ if (ord.isCaseInsensitive()) {
+ out.append("UPPER(");
+ }
+
+ Expression exp = ord.getSortSpec();
+
+ if (exp.getType() == Expression.OBJ_PATH) {
+ appendObjPath(exp);
+ } else if (exp.getType() == Expression.DB_PATH) {
+ appendDbPath(exp);
+ } else {
+ throw new CayenneRuntimeException("Unsupported ordering expression: " + exp);
+ }
+
+ // Close UPPER() modifier
+ if (ord.isCaseInsensitive()) {
+ out.append(")");
+ }
+
+ String columnSQL = out.toString();
+ mainBuffer.append(columnSQL);
+ orderByColumnList.add(columnSQL);
+
+ // "ASC" is a noop, omit it from the query
+ if (!ord.isAscending()) {
+ mainBuffer.append(" DESC");
+ }
+
+ if (it.hasNext()) {
+ mainBuffer.append(", ");
+ }
+ }
+ } finally {
+ this.out = mainBuffer;
+ }
+ }
+
+ /**
+ * Returns the column expressions (not Expressions) used in the order by
+ * clause. E.g., in the case of an case-insensitive order by, an element of
+ * the list would be <code>UPPER(<column reference>)</code>
+ */
+ public List<String> getOrderByColumnList() {
+ return orderByColumnList;
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/81f4fb50/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 4fa0fcc..c5fd13a 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
@@ -43,381 +43,358 @@ import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.collections.Transformer;
/**
- * Translates query qualifier to SQL. Used as a helper class by query translators.
+ * Translates query qualifier to SQL. Used as a helper class by query
+ * translators.
*/
public class QualifierTranslator extends QueryAssemblerHelper implements TraversalHandler {
- protected DataObjectMatchTranslator objectMatchTranslator;
- protected boolean matchingObject;
- protected boolean caseInsensitive;
-
- 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() throws IOException {
- doAppendPart(extractQualifier());
- }
-
- public void setCaseInsensitive(boolean caseInsensitive) {
- this.caseInsensitive = caseInsensitive;
- }
-
- /**
- * Translates query qualifier to SQL WHERE clause. Qualifier is a method parameter.
- *
- * @since 3.0
- */
- protected void doAppendPart(Expression rootNode) throws IOException {
- if (rootNode == null) {
- return;
- }
- rootNode.traverse(this);
- }
-
- protected Expression extractQualifier() {
- 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();
- }
-
- public void finishedChild(Expression node, int childIndex, boolean hasMoreChildren) {
-
- 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 ">>";
- }
-
- public void startNode(Expression node, Expression parentNode) {
- int count = node.getOperandCount();
-
- if (count == 2) {
- // binary nodes are the only ones that currently require this
- detectObjectMatch(node);
- }
-
- try {
-
- if (parenthesisNeeded(node, parentNode)) {
- out.append('(');
- }
-
- if (count == 0) {
- // not all databases handle true/false
- if (node.getType() == Expression.TRUE) {
- out.append("1 = 1");
- }
- if (node.getType() == Expression.FALSE) {
- out.append("1 = 0");
- }
- }
-
- if (count == 1) {
- if (node.getType() == Expression.NEGATIVE)
- out.append('-');
- // ignore POSITIVE - it is a NOOP
- // else if(node.getType() == Expression.POSITIVE)
- // qualBuf.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(");
- }
- }
- catch (IOException ioex) {
- throw new CayenneRuntimeException("Error appending content", ioex);
- }
- }
-
- /**
- * @since 1.1
- */
- public void endNode(Expression node, Expression parentNode) {
+ protected DataObjectMatchTranslator objectMatchTranslator;
+ protected boolean matchingObject;
+ protected boolean caseInsensitive;
+
+ 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;
+ }
+
+ /**
+ * 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() {
+ 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();
+ }
+
+ public void finishedChild(Expression node, int childIndex, boolean hasMoreChildren) {
+
+ 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 ">>";
+ }
+
+ public void startNode(Expression node, Expression parentNode) {
+ int count = node.getOperandCount();
+
+ if (count == 2) {
+ // binary nodes are the only ones that currently require this
+ detectObjectMatch(node);
+ }
+
+ if (parenthesisNeeded(node, parentNode)) {
+ out.append('(');
+ }
+
+ if (count == 0) {
+ // not all databases handle true/false
+ if (node.getType() == Expression.TRUE) {
+ out.append("1 = 1");
+ }
+ if (node.getType() == Expression.FALSE) {
+ out.append("1 = 0");
+ }
+ }
+
+ if (count == 1) {
+ if (node.getType() == Expression.NEGATIVE)
+ out.append('-');
+ // ignore POSITIVE - it is a NOOP
+ // else if(node.getType() == Expression.POSITIVE)
+ // qualBuf.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
+ */
+ public void endNode(Expression node, Expression parentNode) {
try {
// check if we need to use objectMatchTranslator to finish building
@@ -444,128 +421,110 @@ public class QualifierTranslator extends QueryAssemblerHelper implements Travers
if (parenthesisNeeded) {
out.append(')');
}
+ } catch (IOException ioex) {
+ throw new CayenneRuntimeException("Error appending content", ioex);
+ }
+ }
+
+ public void objectNode(Object leaf, Expression parentNode) {
+
+ try {
+ if (parentNode.getType() == Expression.OBJ_PATH) {
+ appendObjPath(parentNode);
+ } else if (parentNode.getType() == Expression.DB_PATH) {
+ appendDbPath(parentNode);
+ } else if (parentNode.getType() == Expression.LIST) {
+ appendList(parentNode, paramsDbType(parentNode));
+ } else {
+ appendLiteral(leaf, paramsDbType(parentNode), parentNode);
+ }
+ } catch (IOException ioex) {
+ throw new CayenneRuntimeException("Error appending content", ioex);
+ }
+ }
+
+ protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
+ 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)
+ return false;
+
+ if (node.getType() == Expression.DB_PATH)
+ return false;
+
+ return true;
+ }
+
+ private final void appendList(Expression listExpr, DbAttribute paramDesc) throws IOException {
+ Iterator<?> it = null;
+ Object list = listExpr.getOperand(0);
+ if (list instanceof List) {
+ it = ((List<?>) list).iterator();
+ } else if (list instanceof Object[]) {
+ it = IteratorUtils.arrayIterator((Object[]) list);
+ } 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);
+ }
+ }
+
+ /**
+ * 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 Transformer {
+
+ public Object transform(Object input) {
+ if (input instanceof ASTObjPath) {
+ return new ASTDbPath(((SimpleNode) input).getOperand(0));
+ }
+ return input;
}
- catch (IOException ioex) {
- throw new CayenneRuntimeException("Error appending content", ioex);
- }
- }
-
- public void objectNode(Object leaf, Expression parentNode) {
-
- try {
- if (parentNode.getType() == Expression.OBJ_PATH) {
- appendObjPath(parentNode);
- }
- else if (parentNode.getType() == Expression.DB_PATH) {
- appendDbPath(parentNode);
- }
- else if (parentNode.getType() == Expression.LIST) {
- appendList(parentNode, paramsDbType(parentNode));
- }
- else {
- appendLiteral(leaf, paramsDbType(parentNode), parentNode);
- }
- }
- catch (IOException ioex) {
- throw new CayenneRuntimeException("Error appending content", ioex);
- }
- }
-
- protected boolean parenthesisNeeded(Expression node, Expression parentNode) {
- 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)
- return false;
-
- if (node.getType() == Expression.DB_PATH)
- return false;
-
- return true;
- }
-
- private final void appendList(Expression listExpr, DbAttribute paramDesc)
- throws IOException {
- Iterator<?> it = null;
- Object list = listExpr.getOperand(0);
- if (list instanceof List) {
- it = ((List<?>) list).iterator();
- }
- else if (list instanceof Object[]) {
- it = IteratorUtils.arrayIterator((Object[]) list);
- }
- 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) throws IOException {
-
- if (!matchingObject) {
- super.processRelTermination(rel, joinType, joinSplitAlias);
- }
- else {
- if (rel.isToMany()) {
- // append joins
- queryAssembler.dbRelationshipAdded(rel, joinType, joinSplitAlias);
- }
- objectMatchTranslator.setRelationship(rel, joinSplitAlias);
- }
- }
-
- /**
- * 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 Transformer {
-
- public Object transform(Object input) {
- if (input instanceof ASTObjPath) {
- return new ASTDbPath(((SimpleNode) input).getOperand(0));
- }
- return input;
- }
- }
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/81f4fb50/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
index d83e6b2..af1ade2 100644
--- 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
@@ -19,21 +19,16 @@
package org.apache.cayenne.access.translator.select;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import org.apache.cayenne.access.DataNode;
+import org.apache.cayenne.access.translator.ParameterBinding;
import org.apache.cayenne.dba.DbAdapter;
-import org.apache.cayenne.log.JdbcEventLogger;
import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.DbRelationship;
import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.map.JoinType;
-import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.query.Query;
import org.apache.cayenne.query.QueryMetadata;
@@ -42,176 +37,127 @@ import org.apache.cayenne.query.QueryMetadata;
*/
public abstract class QueryAssembler {
- protected Query query;
- protected QueryMetadata queryMetadata;
- protected String cachedSqlString;
- protected Connection connection;
- protected DbAdapter adapter;
- protected EntityResolver entityResolver;
- protected JdbcEventLogger logger;
-
- /**
- * Holds PreparedStatement values.
- */
- protected List<Object> values = new ArrayList<Object>();
-
- /**
- * PreparedStatement attributes matching entries in <code>values</code>
- * list.
- */
- protected List<DbAttribute> attributes = new ArrayList<DbAttribute>();
-
- /**
- * The index parameter will be inserted at in parameter list
- */
- protected int parameterIndex;
-
- /**
- * @since 4.0
- */
- public QueryAssembler(Query query, DataNode dataNode, Connection connection) {
- this.logger = dataNode.getJdbcEventLogger();
- this.entityResolver = dataNode.getEntityResolver();
- this.adapter = dataNode.getAdapter();
- this.query = query;
- this.connection = connection;
- this.queryMetadata = query.getMetaData(entityResolver);
- }
-
- /**
- * Returns aliases for the path splits defined in the query.
- *
- * @since 3.0
- */
- protected Map<String, String> getPathAliases() {
- return query.getMetaData(entityResolver).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;
- }
-
- /**
- * @since 3.1
- */
- public JdbcEventLogger getJdbcEventLogger() {
- return logger;
- }
-
- public DbEntity getRootDbEntity() {
- return queryMetadata.getDbEntity();
- }
-
- public ObjEntity getRootEntity() {
- return queryMetadata.getObjEntity();
- }
-
- /**
- * 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 sql string. This is a workhorse method of
- * QueryAssembler. It is called internally from <code>createStatement</code>
- * . Usually there is no need to invoke it explicitly.
- */
- public abstract String createSqlString() throws Exception;
-
- /**
- * 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) {
- attributes.add(parameterIndex, dbAttr);
- values.add(parameterIndex++, anObject);
- }
-
- /**
- * Translates internal query into PreparedStatement.
- */
- public PreparedStatement createStatement() throws Exception {
- long t1 = System.currentTimeMillis();
- String sqlStr = createSqlString();
- logger.logQuery(sqlStr, attributes, values, System.currentTimeMillis() - t1);
- PreparedStatement stmt = connection.prepareStatement(sqlStr);
- initStatement(stmt);
- return stmt;
- }
-
- /**
- * Initializes prepared statements with collected parameters. Called
- * internally from "createStatement". Cayenne users shouldn't normally call
- * it directly.
- */
- protected void initStatement(PreparedStatement stmt) throws Exception {
- if (values != null && values.size() > 0) {
- int len = values.size();
- for (int i = 0; i < len; i++) {
- Object val = values.get(i);
-
- DbAttribute attr = attributes.get(i);
-
- // null DbAttributes are a result of inferior qualifier
- // processing
- // (qualifier can't map parameters to DbAttributes and therefore
- // only supports standard java types now)
- // hence, a special moronic case here:
- if (attr == null) {
- stmt.setObject(i + 1, val);
- } else {
- adapter.bindParameter(stmt, val, i + 1, attr.getType(), attr.getScale());
- }
- }
- }
-
- if (queryMetadata.getStatementFetchSize() != 0) {
- stmt.setFetchSize(queryMetadata.getStatementFetchSize());
- }
- }
+ protected Query query;
+ protected QueryMetadata queryMetadata;
+ protected boolean translated;
+ protected String sql;
+ protected DbAdapter adapter;
+ protected EntityResolver entityResolver;
+ protected List<ParameterBinding> bindings;
+
+ /**
+ * @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<ParameterBinding>();
+ }
+
+ /**
+ * 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) {
+ ParameterBinding binding = new ParameterBinding(dbAttr);
+ binding.setValue(anObject);
+ binding.setStatementPosition(bindings.size() + 1);
+ bindings.add(binding);
+ }
+
+ /**
+ * @since 4.0
+ */
+ public ParameterBinding[] getBindings() {
+ return bindings.toArray(new ParameterBinding[bindings.size()]);
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/81f4fb50/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
index 3152518..e56bc2c 100644
--- 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
@@ -49,449 +49,444 @@ import org.apache.cayenne.util.CayenneMapEntry;
*/
public abstract class QueryAssemblerHelper {
- protected QueryAssembler queryAssembler;
- protected Appendable out;
- protected QuotingStrategy strategy;
-
- /**
- * 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.getRootEntity();
- }
-
- public DbEntity getDbEntity() {
- return queryAssembler.getRootDbEntity();
- }
-
- /**
- * @since 3.0
- */
- public <T extends Appendable> T appendPart(T out) throws IOException {
- this.out = out;
- doAppendPart();
- return out;
- }
-
- /**
- * Sets ouput buffer
- */
- void setOut(Appendable out) {
- this.out = out;
- }
-
- /**
- * @return output buffer
- */
- Appendable getOut() {
- return out;
- }
-
- /**
- * @since 3.0
- */
- protected abstract void doAppendPart() throws IOException;
-
- /**
- * <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) throws IOException {
-
- 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: " + 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) throws IOException {
-
- 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) throws IOException {
- processColumnWithQuoteSqlIdentifiers(dbAttr, null);
- }
-
- protected void processColumnWithQuoteSqlIdentifiers(DbAttribute dbAttr, Expression pathExp) throws IOException {
-
- 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) {
- StringBuilder msg = new StringBuilder();
- 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(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) {
- StringBuilder msg = new StringBuilder();
- 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(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('?');
-
- // 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();
-
- // 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)
- throws IOException {
-
- 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)
- throws IOException {
-
- if (rel.isToMany()) {
- // append joins
- queryAssembler.dbRelationshipAdded(rel, joinType, joinSplitAlias);
- }
-
- // get last DbRelationship on the list
- List<DbJoin> joins = rel.getJoins();
- if (joins.size() != 1) {
- StringBuilder msg = new StringBuilder();
- 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());
- }
-
- DbJoin join = joins.get(0);
-
- DbAttribute attribute = null;
-
- if (rel.isToMany()) {
- DbEntity ent = (DbEntity) join.getRelationship().getTargetEntity();
- Collection<DbAttribute> pk = ent.getPrimaryKeys();
- if (pk.size() != 1) {
- StringBuilder msg = new StringBuilder();
- 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());
- }
-
- attribute = pk.iterator().next();
- } else {
- attribute = join.getSource();
- }
-
- processColumn(attribute);
- }
+ protected QueryAssembler queryAssembler;
+ protected StringBuilder out;
+ protected QuotingStrategy strategy;
+
+ /**
+ * 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: " + 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) {
+ StringBuilder msg = new StringBuilder();
+ 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(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) {
+ StringBuilder msg = new StringBuilder();
+ 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(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 (rel.isToMany()) {
+ // append joins
+ queryAssembler.dbRelationshipAdded(rel, joinType, joinSplitAlias);
+ }
+
+ // get last DbRelationship on the list
+ List<DbJoin> joins = rel.getJoins();
+ if (joins.size() != 1) {
+ StringBuilder msg = new StringBuilder();
+ 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());
+ }
+
+ DbJoin join = joins.get(0);
+
+ DbAttribute attribute = null;
+
+ if (rel.isToMany()) {
+ DbEntity ent = (DbEntity) join.getRelationship().getTargetEntity();
+ Collection<DbAttribute> pk = ent.getPrimaryKeys();
+ if (pk.size() != 1) {
+ StringBuilder msg = new StringBuilder();
+ 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());
+ }
+
+ attribute = pk.iterator().next();
+ } else {
+ attribute = join.getSource();
+ }
+
+ processColumn(attribute);
+ }
}