summaryrefslogtreecommitdiff
path: root/src/main/java/com/amazon
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/amazon')
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java588
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JoinNode.java240
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/JoinNodeBuilder.java117
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/SQLStatement.java1
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/SQLStatementBuilder.java112
-rw-r--r--src/main/java/com/amazon/carbonado/repo/jdbc/WhereBuilder.java254
6 files changed, 726 insertions, 586 deletions
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java
index 393986e..3fbd302 100644
--- a/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JDBCStorage.java
@@ -45,13 +45,10 @@ import com.amazon.carbonado.Transaction;
import com.amazon.carbonado.Trigger;
import com.amazon.carbonado.capability.IndexInfo;
import com.amazon.carbonado.filter.AndFilter;
-import com.amazon.carbonado.filter.ClosedFilter;
-import com.amazon.carbonado.filter.ExistsFilter;
import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.FilterValues;
import com.amazon.carbonado.filter.OrFilter;
import com.amazon.carbonado.filter.PropertyFilter;
-import com.amazon.carbonado.filter.RelOp;
import com.amazon.carbonado.filter.Visitor;
import com.amazon.carbonado.info.ChainedProperty;
import com.amazon.carbonado.info.Direction;
@@ -332,7 +329,7 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
throw mRepository.toFetchException(e);
}
- StatementBuilder<S> selectBuilder = new StatementBuilder<S>(mRepository);
+ SQLStatementBuilder<S> selectBuilder = new SQLStatementBuilder<S>(mRepository);
selectBuilder.append("SELECT ");
// Don't bother using a table alias for one table. With just one table,
@@ -358,7 +355,7 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
selectBuilder.append(" FROM");
- StatementBuilder<S> fromWhereBuilder = new StatementBuilder<S>(mRepository);
+ SQLStatementBuilder<S> fromWhereBuilder = new SQLStatementBuilder<S>(mRepository);
fromWhereBuilder.append(" FROM");
if (alias == null) {
@@ -840,585 +837,4 @@ class JDBCStorage<S extends Storable> extends StandardQueryFactory<S>
return new JDBCQuery(values.getFilter(), values, ordering, executor);
}
}
-
- /**
- * Node in a tree structure describing how tables are joined together.
- */
- private static class JoinNode {
- // Joined property which led to this node. For root node, it is null.
- private final JDBCStorableProperty<?> mProperty;
- private final boolean mOuterJoin;
-
- private final JDBCStorableInfo<?> mInfo;
- private final String mAlias;
-
- private static class SubNodeKey {
- final String mPropertyName;
- final boolean mOuterJoin;
-
- SubNodeKey(String propertyName, boolean outerJoin) {
- mPropertyName = propertyName;
- mOuterJoin = outerJoin;
- }
-
- @Override
- public int hashCode() {
- return mPropertyName.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj instanceof SubNodeKey) {
- SubNodeKey other = (SubNodeKey) obj;
- return mPropertyName.equals(other.mPropertyName)
- && mOuterJoin == other.mOuterJoin;
- }
- return false;
- }
-
- @Override
- public String toString() {
- return "propertyName=" + mPropertyName + ", outerJoin=" + mOuterJoin;
- }
- }
-
- private final Map<SubNodeKey, JoinNode> mSubNodes;
-
- private boolean mAliasRequired;
-
- /**
- * @param alias table alias in SQL statement, i.e. "T1"
- */
- JoinNode(JDBCStorableInfo<?> info, String alias) {
- this(null, false, info, alias);
- }
-
- private JoinNode(JDBCStorableProperty<?> property,
- boolean outerJoin,
- JDBCStorableInfo<?> info,
- String alias)
- {
- mProperty = property;
- mOuterJoin = outerJoin;
- mInfo = info;
- mAlias = alias;
- mSubNodes = new LinkedHashMap<SubNodeKey, JoinNode>();
- }
-
- /**
- * Returns the table alias to use in SQL statement, i.e. "T1"
- */
- public String getAlias() {
- return mAlias;
- }
-
- public String findAliasFor(ChainedProperty<?> chained) {
- return findAliasFor(chained, 0);
- }
-
- private String findAliasFor(ChainedProperty<?> chained, int offset) {
- if ((chained.getChainCount() - offset) <= 0) {
- // At this point in the chain, there are no more joins.
- return mAlias;
- }
- StorableProperty<?> property;
- if (offset == 0) {
- property = chained.getPrimeProperty();
- } else {
- property = chained.getChainedProperty(offset - 1);
- }
- boolean outerJoin = chained.isOuterJoin(offset);
- SubNodeKey key = new SubNodeKey(property.getName(), outerJoin);
- JoinNode subNode = mSubNodes.get(key);
- if (subNode != null) {
- return subNode.findAliasFor(chained, offset + 1);
- }
- return null;
- }
-
- public boolean isAliasRequired() {
- return mAliasRequired || mSubNodes.size() > 0;
- }
-
- /**
- * Appends table name to the given FROM clause builder.
- */
- public void appendTableNameTo(StatementBuilder fromClause) {
- fromClause.append(' ');
- fromClause.append(mInfo.getQualifiedTableName());
- }
-
- /**
- * Appends table name and alias to the given FROM clause builder.
- */
- public void appendTableNameAndAliasTo(StatementBuilder fromClause) {
- appendTableNameTo(fromClause);
- fromClause.append(' ');
- fromClause.append(mAlias);
- }
-
- /**
- * Appends table names, aliases, and joins to the given FROM clause
- * builder.
- */
- public void appendFullJoinTo(StatementBuilder fromClause) {
- appendTableNameAndAliasTo(fromClause);
- appendTailJoinTo(fromClause);
- }
-
- private void appendTailJoinTo(StatementBuilder fromClause) {
- for (JoinNode jn : mSubNodes.values()) {
- if (jn.mOuterJoin) {
- fromClause.append(" LEFT OUTER JOIN");
- } else {
- fromClause.append(" INNER JOIN");
- }
- jn.appendTableNameAndAliasTo(fromClause);
- fromClause.append(" ON ");
- int count = jn.mProperty.getJoinElementCount();
- for (int i=0; i<count; i++) {
- if (i > 0) {
- fromClause.append(" AND ");
- }
- fromClause.append(mAlias);
- fromClause.append('.');
- fromClause.append(jn.mProperty.getInternalJoinElement(i).getColumnName());
- fromClause.append('=');
- fromClause.append(jn.mAlias);
- fromClause.append('.');
- fromClause.append(jn.mProperty.getExternalJoinElement(i).getColumnName());
- }
-
- jn.appendTailJoinTo(fromClause);
- }
- }
-
- public void addJoin(JDBCRepository repository,
- ChainedProperty<?> chained,
- TableAliasGenerator aliasGenerator)
- throws RepositoryException
- {
- addJoin(repository, chained, aliasGenerator, 0);
- }
-
- private void addJoin(JDBCRepository repository,
- ChainedProperty<?> chained,
- TableAliasGenerator aliasGenerator,
- int offset)
- throws RepositoryException
- {
- if ((chained.getChainCount() - offset) <= 0) {
- // At this point in the chain, there are no more joins.
- return;
- }
- StorableProperty<?> property;
- if (offset == 0) {
- property = chained.getPrimeProperty();
- } else {
- property = chained.getChainedProperty(offset - 1);
- }
- boolean outerJoin = chained.isOuterJoin(offset);
- SubNodeKey key = new SubNodeKey(property.getName(), outerJoin);
- JoinNode subNode = mSubNodes.get(key);
- if (subNode == null) {
- JDBCStorableInfo<?> info = repository.examineStorable(property.getJoinedType());
- JDBCStorableProperty<?> jProperty = repository.getJDBCStorableProperty(property);
- subNode = new JoinNode(jProperty, outerJoin, info, aliasGenerator.nextAlias());
- mSubNodes.put(key, subNode);
- }
- subNode.addJoin(repository, chained, aliasGenerator, offset + 1);
- }
-
- public void aliasIsRequired() {
- mAliasRequired = true;
- }
-
- public String toString() {
- StringBuilder b = new StringBuilder();
- b.append("{table=");
- b.append(mInfo.getQualifiedTableName());
- b.append(", alias=");
- b.append(mAlias);
- if (mSubNodes.size() > 0) {
- b.append(", subNodes=");
- b.append(mSubNodes);
- }
- b.append('}');
- return b.toString();
- }
- }
-
- /**
- * Filter visitor that constructs a JoinNode tree.
- */
- private static class JoinNodeBuilder<S extends Storable> extends Visitor<S, Object, Object> {
- private final JDBCRepository mRepository;
- private final TableAliasGenerator mAliasGenerator;
- private final JoinNode mRootJoinNode;
-
- JoinNodeBuilder(JDBCRepository repository,
- JDBCStorableInfo<S> info,
- TableAliasGenerator aliasGenerator)
- {
- mRepository = repository;
- mAliasGenerator = aliasGenerator;
- mRootJoinNode = new JoinNode(info, aliasGenerator.nextAlias());
- }
-
- public JoinNode getRootJoinNode() {
- return mRootJoinNode;
- }
-
- /**
- * Processes the given property orderings and ensures that they are
- * part of the JoinNode tree.
- *
- * @throws UndeclaredThrowableException wraps a RepositoryException
- */
- public void captureOrderings(OrderingList<?> ordering) {
- try {
- if (ordering != null) {
- for (OrderedProperty<?> orderedProperty : ordering) {
- ChainedProperty<?> chained = orderedProperty.getChainedProperty();
- if (!chained.isDerived()) {
- mRootJoinNode.addJoin(mRepository, chained, mAliasGenerator);
- }
- }
- }
- } catch (RepositoryException e) {
- throw new UndeclaredThrowableException(e);
- }
- }
-
- /**
- * @throws UndeclaredThrowableException wraps a RepositoryException
- * since RepositoryException cannot be thrown directly
- */
- @Override
- public Object visit(PropertyFilter<S> filter, Object param) {
- try {
- visit(filter);
- return null;
- } catch (RepositoryException e) {
- throw new UndeclaredThrowableException(e);
- }
- }
-
- private void visit(PropertyFilter<S> filter) throws RepositoryException {
- ChainedProperty<S> chained = filter.getChainedProperty();
- mRootJoinNode.addJoin(mRepository, chained, mAliasGenerator);
- }
-
- /**
- * @throws UndeclaredThrowableException wraps a RepositoryException
- * since RepositoryException cannot be thrown directly
- */
- @Override
- public Object visit(ExistsFilter<S> filter, Object param) {
- try {
- visit(filter);
- return null;
- } catch (RepositoryException e) {
- throw new UndeclaredThrowableException(e);
- }
- }
-
- private void visit(ExistsFilter<S> filter) throws RepositoryException {
- mRootJoinNode.aliasIsRequired();
- ChainedProperty<S> chained = filter.getChainedProperty();
- mRootJoinNode.addJoin(mRepository, chained, mAliasGenerator);
- }
- }
-
- private static class StatementBuilder<S extends Storable> {
- private final JDBCRepository mRepository;
-
- private List<SQLStatement<S>> mStatements;
- private StringBuilder mLiteralBuilder;
-
- StatementBuilder(JDBCRepository repository) {
- mRepository = repository;
- mStatements = new ArrayList<SQLStatement<S>>();
- mLiteralBuilder = new StringBuilder();
- }
-
- public SQLStatement<S> build() {
- if (mStatements.size() == 0 || mLiteralBuilder.length() > 0) {
- mStatements.add(new LiteralStatement<S>(mLiteralBuilder.toString()));
- mLiteralBuilder.setLength(0);
- }
- if (mStatements.size() == 1) {
- return mStatements.get(0);
- } else {
- return new CompositeStatement<S>(mStatements);
- }
- }
-
- public void append(char c) {
- mLiteralBuilder.append(c);
- }
-
- public void append(String str) {
- mLiteralBuilder.append(str);
- }
-
- public void append(LiteralStatement<S> statement) {
- append(statement.toString());
- }
-
- public void append(SQLStatement<S> statement) {
- if (statement instanceof LiteralStatement) {
- append((LiteralStatement<S>) statement);
- } else {
- mStatements.add(new LiteralStatement<S>(mLiteralBuilder.toString()));
- mLiteralBuilder.setLength(0);
- mStatements.add(statement);
- }
- }
-
- public void appendColumn(JoinNode jn, ChainedProperty<?> chained)
- throws FetchException
- {
- String alias;
- if (jn == null) {
- alias = null;
- } else {
- alias = jn.findAliasFor(chained);
- }
- if (alias != null) {
- mLiteralBuilder.append(alias);
- mLiteralBuilder.append('.');
- }
- StorableProperty<?> property = chained.getLastProperty();
- JDBCStorableProperty<?> jProperty;
- try {
- jProperty = mRepository.getJDBCStorableProperty(property);
- } catch (RepositoryException e) {
- throw mRepository.toFetchException(e);
- }
- if (jProperty.isJoin()) {
- throw new UnsupportedOperationException
- ("Join property doesn't have a corresponding column: " + chained);
- }
- mLiteralBuilder.append(jProperty.getColumnName());
- }
-
- JDBCRepository getRepository() {
- return mRepository;
- }
- }
-
- private static class WhereBuilder<S extends Storable>
- extends Visitor<S, FetchException, Object>
- {
- private final StatementBuilder mStatementBuilder;
- private final JoinNode mJoinNode;
- private final TableAliasGenerator mAliasGenerator;
-
- private List<PropertyFilter<S>> mPropertyFilters;
- private List<Boolean> mPropertyFilterNullable;
-
- /**
- * @param aliasGenerator used for supporting "EXISTS" filter
- */
- WhereBuilder(StatementBuilder statementBuilder, JoinNode jn,
- TableAliasGenerator aliasGenerator)
- {
- mStatementBuilder = statementBuilder;
- mJoinNode = jn;
- mAliasGenerator = aliasGenerator;
- mPropertyFilters = new ArrayList<PropertyFilter<S>>();
- mPropertyFilterNullable = new ArrayList<Boolean>();
- }
-
- @SuppressWarnings("unchecked")
- public PropertyFilter<S>[] getPropertyFilters() {
- return mPropertyFilters.toArray(new PropertyFilter[mPropertyFilters.size()]);
- }
-
- public boolean[] getPropertyFilterNullable() {
- boolean[] array = new boolean[mPropertyFilterNullable.size()];
- for (int i=0; i<array.length; i++) {
- array[i] = mPropertyFilterNullable.get(i);
- }
- return array;
- }
-
- @Override
- public FetchException visit(OrFilter<S> filter, Object param) {
- FetchException e;
- mStatementBuilder.append('(');
- e = filter.getLeftFilter().accept(this, null);
- if (e != null) {
- return e;
- }
- mStatementBuilder.append(" OR ");
- e = filter.getRightFilter().accept(this, null);
- if (e != null) {
- return e;
- }
- mStatementBuilder.append(')');
- return null;
- }
-
- @Override
- public FetchException visit(AndFilter<S> filter, Object param) {
- FetchException e;
- mStatementBuilder.append('(');
- e = filter.getLeftFilter().accept(this, null);
- if (e != null) {
- return e;
- }
- mStatementBuilder.append(" AND ");
- e = filter.getRightFilter().accept(this, null);
- if (e != null) {
- return e;
- }
- mStatementBuilder.append(')');
- return null;
- }
-
- @Override
- public FetchException visit(PropertyFilter<S> filter, Object param) {
- try {
- mStatementBuilder.appendColumn(mJoinNode, filter.getChainedProperty());
- } catch (FetchException e) {
- return e;
- }
-
- if (!filter.isConstant()) {
- addBindParameter(filter);
- } else {
- RelOp op = filter.getOperator();
-
- Object constant = filter.constant();
- if (constant == null) {
- if (op == RelOp.EQ) {
- mStatementBuilder.append("IS NULL");
- } else if (op == RelOp.NE) {
- mStatementBuilder.append("IS NOT NULL");
- } else {
- mStatementBuilder.append(sqlOperatorFor(op));
- mStatementBuilder.append("NULL");
- }
- } else if (filter.getType() == String.class) {
- mStatementBuilder.append(sqlOperatorFor(op));
- mStatementBuilder.append('\'');
- mStatementBuilder.append(String.valueOf(constant).replace("'", "''"));
- mStatementBuilder.append('\'');
- } else if (Number.class.isAssignableFrom(filter.getBoxedType())) {
- mStatementBuilder.append(sqlOperatorFor(op));
- mStatementBuilder.append(String.valueOf(constant));
- } else {
- // Don't try to create literal for special type. Instead,
- // fallback to bind parameter and let JDBC driver do the work.
- addBindParameter(filter);
- }
- }
-
- return null;
- }
-
- @Override
- public FetchException visit(ExistsFilter<S> filter, Object param) {
- if (filter.isNotExists()) {
- mStatementBuilder.append("NOT ");
- }
- mStatementBuilder.append("EXISTS (SELECT * FROM");
-
- ChainedProperty<S> chained = filter.getChainedProperty();
-
- JDBCStorableInfo<?> oneToManyInfo;
- JDBCStorableProperty<?> oneToMany;
-
- final JDBCRepository repo = mStatementBuilder.getRepository();
- try {
- StorableProperty<?> lastProp = chained.getLastProperty();
- oneToManyInfo = repo.examineStorable(lastProp.getJoinedType());
- oneToMany = repo.getJDBCStorableProperty(lastProp);
- } catch (RepositoryException e) {
- return repo.toFetchException(e);
- }
-
- Filter<?> subFilter = filter.getSubFilter();
-
- JoinNode oneToManyNode;
- try {
- JoinNodeBuilder jnb =
- new JoinNodeBuilder(repo, oneToManyInfo, mAliasGenerator);
- if (subFilter != null) {
- subFilter.accept(jnb, null);
- }
- oneToManyNode = jnb.getRootJoinNode();
- } catch (UndeclaredThrowableException e) {
- return repo.toFetchException(e);
- }
-
- oneToManyNode.appendFullJoinTo(mStatementBuilder);
-
- mStatementBuilder.append(" WHERE ");
-
- int count = oneToMany.getJoinElementCount();
- for (int i=0; i<count; i++) {
- if (i > 0) {
- mStatementBuilder.append(" AND ");
- }
- mStatementBuilder.append(oneToManyNode.getAlias());
- mStatementBuilder.append('.');
- mStatementBuilder.append(oneToMany.getInternalJoinElement(i).getColumnName());
- mStatementBuilder.append('=');
- mStatementBuilder.append(mJoinNode.findAliasFor(chained));
- mStatementBuilder.append('.');
- mStatementBuilder.append(oneToMany.getExternalJoinElement(i).getColumnName());
- }
-
- if (subFilter != null && !subFilter.isOpen()) {
- mStatementBuilder.append(" AND (");
- WhereBuilder wb = new WhereBuilder
- (mStatementBuilder, oneToManyNode, mAliasGenerator);
- FetchException e = (FetchException) subFilter.accept(wb, null);
- if (e != null) {
- return e;
- }
- mStatementBuilder.append(')');
- }
-
- mStatementBuilder.append(')');
-
- return null;
- }
-
- @Override
- public FetchException visit(ClosedFilter<S> filter, Object param) {
- mStatementBuilder.append("1=0");
- return null;
- }
-
- private void addBindParameter(PropertyFilter<S> filter) {
- RelOp op = filter.getOperator();
- StorableProperty<?> property = filter.getChainedProperty().getLastProperty();
-
- mPropertyFilters.add(filter);
-
- if (property.isNullable() && (op == RelOp.EQ || op == RelOp.NE)) {
- mPropertyFilterNullable.add(true);
- mStatementBuilder.append(new NullablePropertyStatement<S>(filter, op == RelOp.EQ));
- } else {
- mPropertyFilterNullable.add(false);
- mStatementBuilder.append(sqlOperatorFor(op));
- mStatementBuilder.append('?');
- }
- }
-
- private String sqlOperatorFor(RelOp op) {
- if (op == RelOp.NE) {
- return "<>";
- } else {
- return op.toString();
- }
- }
- }
}
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JoinNode.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JoinNode.java
new file mode 100644
index 0000000..d5a2ba4
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JoinNode.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2007 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * Licensed 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 com.amazon.carbonado.repo.jdbc;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import com.amazon.carbonado.RepositoryException;
+
+import com.amazon.carbonado.info.ChainedProperty;
+import com.amazon.carbonado.info.StorableProperty;
+
+/**
+ * Node in a tree structure describing how tables are joined together.
+ *
+ * @author Brian S O'Neill
+ * @see JoinNodeBuilder
+ */
+class JoinNode {
+ // Joined property which led to this node. For root node, it is null.
+ private final JDBCStorableProperty<?> mProperty;
+ private final boolean mOuterJoin;
+
+ private final JDBCStorableInfo<?> mInfo;
+ private final String mAlias;
+
+ private final Map<SubNodeKey, JoinNode> mSubNodes;
+
+ private boolean mAliasRequired;
+
+ /**
+ * @param alias table alias in SQL statement, i.e. "T1"
+ */
+ JoinNode(JDBCStorableInfo<?> info, String alias) {
+ this(null, false, info, alias);
+ }
+
+ private JoinNode(JDBCStorableProperty<?> property,
+ boolean outerJoin,
+ JDBCStorableInfo<?> info,
+ String alias)
+ {
+ mProperty = property;
+ mOuterJoin = outerJoin;
+ mInfo = info;
+ mAlias = alias;
+ mSubNodes = new LinkedHashMap<SubNodeKey, JoinNode>();
+ }
+
+ /**
+ * Returns the table alias to use in SQL statement, i.e. "T1"
+ */
+ public String getAlias() {
+ return mAlias;
+ }
+
+ public String findAliasFor(ChainedProperty<?> chained) {
+ return findAliasFor(chained, 0);
+ }
+
+ private String findAliasFor(ChainedProperty<?> chained, int offset) {
+ if ((chained.getChainCount() - offset) <= 0) {
+ // At this point in the chain, there are no more joins.
+ return mAlias;
+ }
+ StorableProperty<?> property;
+ if (offset == 0) {
+ property = chained.getPrimeProperty();
+ } else {
+ property = chained.getChainedProperty(offset - 1);
+ }
+ boolean outerJoin = chained.isOuterJoin(offset);
+ SubNodeKey key = new SubNodeKey(property.getName(), outerJoin);
+ JoinNode subNode = mSubNodes.get(key);
+ if (subNode != null) {
+ return subNode.findAliasFor(chained, offset + 1);
+ }
+ return null;
+ }
+
+ public boolean isAliasRequired() {
+ return mAliasRequired || mSubNodes.size() > 0;
+ }
+
+ /**
+ * Appends table name to the given FROM clause builder.
+ */
+ public void appendTableNameTo(SQLStatementBuilder fromClause) {
+ fromClause.append(' ');
+ fromClause.append(mInfo.getQualifiedTableName());
+ }
+
+ /**
+ * Appends table name and alias to the given FROM clause builder.
+ */
+ public void appendTableNameAndAliasTo(SQLStatementBuilder fromClause) {
+ appendTableNameTo(fromClause);
+ fromClause.append(' ');
+ fromClause.append(mAlias);
+ }
+
+ /**
+ * Appends table names, aliases, and joins to the given FROM clause
+ * builder.
+ */
+ public void appendFullJoinTo(SQLStatementBuilder fromClause) {
+ appendTableNameAndAliasTo(fromClause);
+ appendTailJoinTo(fromClause);
+ }
+
+ private void appendTailJoinTo(SQLStatementBuilder fromClause) {
+ for (JoinNode jn : mSubNodes.values()) {
+ if (jn.mOuterJoin) {
+ fromClause.append(" LEFT OUTER JOIN");
+ } else {
+ fromClause.append(" INNER JOIN");
+ }
+ jn.appendTableNameAndAliasTo(fromClause);
+ fromClause.append(" ON ");
+ int count = jn.mProperty.getJoinElementCount();
+ for (int i=0; i<count; i++) {
+ if (i > 0) {
+ fromClause.append(" AND ");
+ }
+ fromClause.append(mAlias);
+ fromClause.append('.');
+ fromClause.append(jn.mProperty.getInternalJoinElement(i).getColumnName());
+ fromClause.append('=');
+ fromClause.append(jn.mAlias);
+ fromClause.append('.');
+ fromClause.append(jn.mProperty.getExternalJoinElement(i).getColumnName());
+ }
+
+ jn.appendTailJoinTo(fromClause);
+ }
+ }
+
+ public void addJoin(JDBCRepository repository,
+ ChainedProperty<?> chained,
+ TableAliasGenerator aliasGenerator)
+ throws RepositoryException
+ {
+ addJoin(repository, chained, aliasGenerator, 0);
+ }
+
+ private void addJoin(JDBCRepository repository,
+ ChainedProperty<?> chained,
+ TableAliasGenerator aliasGenerator,
+ int offset)
+ throws RepositoryException
+ {
+ if ((chained.getChainCount() - offset) <= 0) {
+ // At this point in the chain, there are no more joins.
+ return;
+ }
+ StorableProperty<?> property;
+ if (offset == 0) {
+ property = chained.getPrimeProperty();
+ } else {
+ property = chained.getChainedProperty(offset - 1);
+ }
+ boolean outerJoin = chained.isOuterJoin(offset);
+ SubNodeKey key = new SubNodeKey(property.getName(), outerJoin);
+ JoinNode subNode = mSubNodes.get(key);
+ if (subNode == null) {
+ JDBCStorableInfo<?> info = repository.examineStorable(property.getJoinedType());
+ JDBCStorableProperty<?> jProperty = repository.getJDBCStorableProperty(property);
+ subNode = new JoinNode(jProperty, outerJoin, info, aliasGenerator.nextAlias());
+ mSubNodes.put(key, subNode);
+ }
+ subNode.addJoin(repository, chained, aliasGenerator, offset + 1);
+ }
+
+ public void aliasIsRequired() {
+ mAliasRequired = true;
+ }
+
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("{table=");
+ b.append(mInfo.getQualifiedTableName());
+ b.append(", alias=");
+ b.append(mAlias);
+ if (mSubNodes.size() > 0) {
+ b.append(", subNodes=");
+ b.append(mSubNodes);
+ }
+ b.append('}');
+ return b.toString();
+ }
+
+ private static class SubNodeKey {
+ final String mPropertyName;
+ final boolean mOuterJoin;
+
+ SubNodeKey(String propertyName, boolean outerJoin) {
+ mPropertyName = propertyName;
+ mOuterJoin = outerJoin;
+ }
+
+ @Override
+ public int hashCode() {
+ return mPropertyName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof SubNodeKey) {
+ SubNodeKey other = (SubNodeKey) obj;
+ return mPropertyName.equals(other.mPropertyName)
+ && mOuterJoin == other.mOuterJoin;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "propertyName=" + mPropertyName + ", outerJoin=" + mOuterJoin;
+ }
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/JoinNodeBuilder.java b/src/main/java/com/amazon/carbonado/repo/jdbc/JoinNodeBuilder.java
new file mode 100644
index 0000000..d310be6
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/JoinNodeBuilder.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * Licensed 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 com.amazon.carbonado.repo.jdbc;
+
+import java.lang.reflect.UndeclaredThrowableException;
+
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+
+import com.amazon.carbonado.filter.ExistsFilter;
+import com.amazon.carbonado.filter.PropertyFilter;
+import com.amazon.carbonado.filter.Visitor;
+
+import com.amazon.carbonado.info.ChainedProperty;
+import com.amazon.carbonado.info.OrderedProperty;
+
+import com.amazon.carbonado.qe.OrderingList;
+
+/**
+ * Filter visitor that constructs a JoinNode tree.
+ *
+ * @author Brian S O'Neill
+ */
+class JoinNodeBuilder<S extends Storable> extends Visitor<S, Object, Object> {
+ private final JDBCRepository mRepository;
+ private final TableAliasGenerator mAliasGenerator;
+ private final JoinNode mRootJoinNode;
+
+ JoinNodeBuilder(JDBCRepository repository,
+ JDBCStorableInfo<S> info,
+ TableAliasGenerator aliasGenerator)
+ {
+ mRepository = repository;
+ mAliasGenerator = aliasGenerator;
+ mRootJoinNode = new JoinNode(info, aliasGenerator.nextAlias());
+ }
+
+ public JoinNode getRootJoinNode() {
+ return mRootJoinNode;
+ }
+
+ /**
+ * Processes the given property orderings and ensures that they are
+ * part of the JoinNode tree.
+ *
+ * @throws UndeclaredThrowableException wraps a RepositoryException
+ */
+ public void captureOrderings(OrderingList<?> ordering) {
+ try {
+ if (ordering != null) {
+ for (OrderedProperty<?> orderedProperty : ordering) {
+ ChainedProperty<?> chained = orderedProperty.getChainedProperty();
+ if (!chained.isDerived()) {
+ mRootJoinNode.addJoin(mRepository, chained, mAliasGenerator);
+ }
+ }
+ }
+ } catch (RepositoryException e) {
+ throw new UndeclaredThrowableException(e);
+ }
+ }
+
+ /**
+ * @throws UndeclaredThrowableException wraps a RepositoryException
+ * since RepositoryException cannot be thrown directly
+ */
+ @Override
+ public Object visit(PropertyFilter<S> filter, Object param) {
+ try {
+ visit(filter);
+ return null;
+ } catch (RepositoryException e) {
+ throw new UndeclaredThrowableException(e);
+ }
+ }
+
+ private void visit(PropertyFilter<S> filter) throws RepositoryException {
+ ChainedProperty<S> chained = filter.getChainedProperty();
+ mRootJoinNode.addJoin(mRepository, chained, mAliasGenerator);
+ }
+
+ /**
+ * @throws UndeclaredThrowableException wraps a RepositoryException
+ * since RepositoryException cannot be thrown directly
+ */
+ @Override
+ public Object visit(ExistsFilter<S> filter, Object param) {
+ try {
+ visit(filter);
+ return null;
+ } catch (RepositoryException e) {
+ throw new UndeclaredThrowableException(e);
+ }
+ }
+
+ private void visit(ExistsFilter<S> filter) throws RepositoryException {
+ mRootJoinNode.aliasIsRequired();
+ ChainedProperty<S> chained = filter.getChainedProperty();
+ mRootJoinNode.addJoin(mRepository, chained, mAliasGenerator);
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/SQLStatement.java b/src/main/java/com/amazon/carbonado/repo/jdbc/SQLStatement.java
index 0317e3f..92308f7 100644
--- a/src/main/java/com/amazon/carbonado/repo/jdbc/SQLStatement.java
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/SQLStatement.java
@@ -26,6 +26,7 @@ import com.amazon.carbonado.filter.FilterValues;
* Simple DOM representing a SQL statement.
*
* @author Brian S O'Neill
+ * @see SQLStatementBuilder
*/
abstract class SQLStatement<S extends Storable> {
public abstract int maxLength();
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/SQLStatementBuilder.java b/src/main/java/com/amazon/carbonado/repo/jdbc/SQLStatementBuilder.java
new file mode 100644
index 0000000..5045bf5
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/SQLStatementBuilder.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2007 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * Licensed 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 com.amazon.carbonado.repo.jdbc;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+
+import com.amazon.carbonado.info.ChainedProperty;
+import com.amazon.carbonado.info.StorableProperty;
+
+/**
+ *
+ *
+ * @author Brian S O'Neill
+ */
+class SQLStatementBuilder<S extends Storable> {
+ private final JDBCRepository mRepository;
+
+ private List<SQLStatement<S>> mStatements;
+ private StringBuilder mLiteralBuilder;
+
+ SQLStatementBuilder(JDBCRepository repository) {
+ mRepository = repository;
+ mStatements = new ArrayList<SQLStatement<S>>();
+ mLiteralBuilder = new StringBuilder();
+ }
+
+ public SQLStatement<S> build() {
+ if (mStatements.size() == 0 || mLiteralBuilder.length() > 0) {
+ mStatements.add(new LiteralStatement<S>(mLiteralBuilder.toString()));
+ mLiteralBuilder.setLength(0);
+ }
+ if (mStatements.size() == 1) {
+ return mStatements.get(0);
+ } else {
+ return new CompositeStatement<S>(mStatements);
+ }
+ }
+
+ public void append(char c) {
+ mLiteralBuilder.append(c);
+ }
+
+ public void append(String str) {
+ mLiteralBuilder.append(str);
+ }
+
+ public void append(LiteralStatement<S> statement) {
+ append(statement.toString());
+ }
+
+ public void append(SQLStatement<S> statement) {
+ if (statement instanceof LiteralStatement) {
+ append((LiteralStatement<S>) statement);
+ } else {
+ mStatements.add(new LiteralStatement<S>(mLiteralBuilder.toString()));
+ mLiteralBuilder.setLength(0);
+ mStatements.add(statement);
+ }
+ }
+
+ public void appendColumn(JoinNode jn, ChainedProperty<?> chained)
+ throws FetchException
+ {
+ String alias;
+ if (jn == null) {
+ alias = null;
+ } else {
+ alias = jn.findAliasFor(chained);
+ }
+ if (alias != null) {
+ mLiteralBuilder.append(alias);
+ mLiteralBuilder.append('.');
+ }
+ StorableProperty<?> property = chained.getLastProperty();
+ JDBCStorableProperty<?> jProperty;
+ try {
+ jProperty = mRepository.getJDBCStorableProperty(property);
+ } catch (RepositoryException e) {
+ throw mRepository.toFetchException(e);
+ }
+ if (jProperty.isJoin()) {
+ throw new UnsupportedOperationException
+ ("Join property doesn't have a corresponding column: " + chained);
+ }
+ mLiteralBuilder.append(jProperty.getColumnName());
+ }
+
+ JDBCRepository getRepository() {
+ return mRepository;
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/repo/jdbc/WhereBuilder.java b/src/main/java/com/amazon/carbonado/repo/jdbc/WhereBuilder.java
new file mode 100644
index 0000000..80f895e
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/repo/jdbc/WhereBuilder.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2007 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * Licensed 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 com.amazon.carbonado.repo.jdbc;
+
+import java.lang.reflect.UndeclaredThrowableException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.amazon.carbonado.FetchException;
+import com.amazon.carbonado.RepositoryException;
+import com.amazon.carbonado.Storable;
+
+import com.amazon.carbonado.filter.AndFilter;
+import com.amazon.carbonado.filter.ClosedFilter;
+import com.amazon.carbonado.filter.ExistsFilter;
+import com.amazon.carbonado.filter.Filter;
+import com.amazon.carbonado.filter.OrFilter;
+import com.amazon.carbonado.filter.PropertyFilter;
+import com.amazon.carbonado.filter.RelOp;
+import com.amazon.carbonado.filter.Visitor;
+
+import com.amazon.carbonado.info.ChainedProperty;
+import com.amazon.carbonado.info.StorableProperty;
+
+/**
+ *
+ *
+ * @author Brian S O'Neill
+ */
+class WhereBuilder<S extends Storable> extends Visitor<S, FetchException, Object> {
+ private final SQLStatementBuilder mStatementBuilder;
+ private final JoinNode mJoinNode;
+ private final TableAliasGenerator mAliasGenerator;
+
+ private List<PropertyFilter<S>> mPropertyFilters;
+ private List<Boolean> mPropertyFilterNullable;
+
+ /**
+ * @param aliasGenerator used for supporting "EXISTS" filter
+ */
+ WhereBuilder(SQLStatementBuilder statementBuilder, JoinNode jn,
+ TableAliasGenerator aliasGenerator)
+ {
+ mStatementBuilder = statementBuilder;
+ mJoinNode = jn;
+ mAliasGenerator = aliasGenerator;
+ mPropertyFilters = new ArrayList<PropertyFilter<S>>();
+ mPropertyFilterNullable = new ArrayList<Boolean>();
+ }
+
+ @SuppressWarnings("unchecked")
+ public PropertyFilter<S>[] getPropertyFilters() {
+ return mPropertyFilters.toArray(new PropertyFilter[mPropertyFilters.size()]);
+ }
+
+ public boolean[] getPropertyFilterNullable() {
+ boolean[] array = new boolean[mPropertyFilterNullable.size()];
+ for (int i=0; i<array.length; i++) {
+ array[i] = mPropertyFilterNullable.get(i);
+ }
+ return array;
+ }
+
+ @Override
+ public FetchException visit(OrFilter<S> filter, Object param) {
+ FetchException e;
+ mStatementBuilder.append('(');
+ e = filter.getLeftFilter().accept(this, null);
+ if (e != null) {
+ return e;
+ }
+ mStatementBuilder.append(" OR ");
+ e = filter.getRightFilter().accept(this, null);
+ if (e != null) {
+ return e;
+ }
+ mStatementBuilder.append(')');
+ return null;
+ }
+
+ @Override
+ public FetchException visit(AndFilter<S> filter, Object param) {
+ FetchException e;
+ mStatementBuilder.append('(');
+ e = filter.getLeftFilter().accept(this, null);
+ if (e != null) {
+ return e;
+ }
+ mStatementBuilder.append(" AND ");
+ e = filter.getRightFilter().accept(this, null);
+ if (e != null) {
+ return e;
+ }
+ mStatementBuilder.append(')');
+ return null;
+ }
+
+ @Override
+ public FetchException visit(PropertyFilter<S> filter, Object param) {
+ try {
+ mStatementBuilder.appendColumn(mJoinNode, filter.getChainedProperty());
+ } catch (FetchException e) {
+ return e;
+ }
+
+ if (!filter.isConstant()) {
+ addBindParameter(filter);
+ } else {
+ RelOp op = filter.getOperator();
+
+ Object constant = filter.constant();
+ if (constant == null) {
+ if (op == RelOp.EQ) {
+ mStatementBuilder.append("IS NULL");
+ } else if (op == RelOp.NE) {
+ mStatementBuilder.append("IS NOT NULL");
+ } else {
+ mStatementBuilder.append(sqlOperatorFor(op));
+ mStatementBuilder.append("NULL");
+ }
+ } else if (filter.getType() == String.class) {
+ mStatementBuilder.append(sqlOperatorFor(op));
+ mStatementBuilder.append('\'');
+ mStatementBuilder.append(String.valueOf(constant).replace("'", "''"));
+ mStatementBuilder.append('\'');
+ } else if (Number.class.isAssignableFrom(filter.getBoxedType())) {
+ mStatementBuilder.append(sqlOperatorFor(op));
+ mStatementBuilder.append(String.valueOf(constant));
+ } else {
+ // Don't try to create literal for special type. Instead,
+ // fallback to bind parameter and let JDBC driver do the work.
+ addBindParameter(filter);
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public FetchException visit(ExistsFilter<S> filter, Object param) {
+ if (filter.isNotExists()) {
+ mStatementBuilder.append("NOT ");
+ }
+ mStatementBuilder.append("EXISTS (SELECT * FROM");
+
+ ChainedProperty<S> chained = filter.getChainedProperty();
+
+ JDBCStorableInfo<?> oneToManyInfo;
+ JDBCStorableProperty<?> oneToMany;
+
+ final JDBCRepository repo = mStatementBuilder.getRepository();
+ try {
+ StorableProperty<?> lastProp = chained.getLastProperty();
+ oneToManyInfo = repo.examineStorable(lastProp.getJoinedType());
+ oneToMany = repo.getJDBCStorableProperty(lastProp);
+ } catch (RepositoryException e) {
+ return repo.toFetchException(e);
+ }
+
+ Filter<?> subFilter = filter.getSubFilter();
+
+ JoinNode oneToManyNode;
+ try {
+ JoinNodeBuilder jnb =
+ new JoinNodeBuilder(repo, oneToManyInfo, mAliasGenerator);
+ if (subFilter != null) {
+ subFilter.accept(jnb, null);
+ }
+ oneToManyNode = jnb.getRootJoinNode();
+ } catch (UndeclaredThrowableException e) {
+ return repo.toFetchException(e);
+ }
+
+ oneToManyNode.appendFullJoinTo(mStatementBuilder);
+
+ mStatementBuilder.append(" WHERE ");
+
+ int count = oneToMany.getJoinElementCount();
+ for (int i=0; i<count; i++) {
+ if (i > 0) {
+ mStatementBuilder.append(" AND ");
+ }
+ mStatementBuilder.append(oneToManyNode.getAlias());
+ mStatementBuilder.append('.');
+ mStatementBuilder.append(oneToMany.getInternalJoinElement(i).getColumnName());
+ mStatementBuilder.append('=');
+ mStatementBuilder.append(mJoinNode.findAliasFor(chained));
+ mStatementBuilder.append('.');
+ mStatementBuilder.append(oneToMany.getExternalJoinElement(i).getColumnName());
+ }
+
+ if (subFilter != null && !subFilter.isOpen()) {
+ mStatementBuilder.append(" AND (");
+ WhereBuilder wb = new WhereBuilder
+ (mStatementBuilder, oneToManyNode, mAliasGenerator);
+ FetchException e = (FetchException) subFilter.accept(wb, null);
+ if (e != null) {
+ return e;
+ }
+ mStatementBuilder.append(')');
+ }
+
+ mStatementBuilder.append(')');
+
+ return null;
+ }
+
+ @Override
+ public FetchException visit(ClosedFilter<S> filter, Object param) {
+ mStatementBuilder.append("1=0");
+ return null;
+ }
+
+ private void addBindParameter(PropertyFilter<S> filter) {
+ RelOp op = filter.getOperator();
+ StorableProperty<?> property = filter.getChainedProperty().getLastProperty();
+
+ mPropertyFilters.add(filter);
+
+ if (property.isNullable() && (op == RelOp.EQ || op == RelOp.NE)) {
+ mPropertyFilterNullable.add(true);
+ mStatementBuilder.append(new NullablePropertyStatement<S>(filter, op == RelOp.EQ));
+ } else {
+ mPropertyFilterNullable.add(false);
+ mStatementBuilder.append(sqlOperatorFor(op));
+ mStatementBuilder.append('?');
+ }
+ }
+
+ private String sqlOperatorFor(RelOp op) {
+ if (op == RelOp.NE) {
+ return "<>";
+ } else {
+ return op.toString();
+ }
+ }
+}