From 70288091272e93e95747d01287671b1547adb45c Mon Sep 17 00:00:00 2001 From: "Brian S. O'Neill" Date: Sun, 4 Nov 2007 16:49:02 +0000 Subject: Moved static inner classes to separate files. --- .../amazon/carbonado/repo/jdbc/JDBCStorage.java | 588 +-------------------- .../com/amazon/carbonado/repo/jdbc/JoinNode.java | 240 +++++++++ .../carbonado/repo/jdbc/JoinNodeBuilder.java | 117 ++++ .../amazon/carbonado/repo/jdbc/SQLStatement.java | 1 + .../carbonado/repo/jdbc/SQLStatementBuilder.java | 112 ++++ .../amazon/carbonado/repo/jdbc/WhereBuilder.java | 254 +++++++++ 6 files changed, 726 insertions(+), 586 deletions(-) create mode 100644 src/main/java/com/amazon/carbonado/repo/jdbc/JoinNode.java create mode 100644 src/main/java/com/amazon/carbonado/repo/jdbc/JoinNodeBuilder.java create mode 100644 src/main/java/com/amazon/carbonado/repo/jdbc/SQLStatementBuilder.java create mode 100644 src/main/java/com/amazon/carbonado/repo/jdbc/WhereBuilder.java (limited to 'src/main/java/com/amazon/carbonado/repo/jdbc') 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 extends StandardQueryFactory throw mRepository.toFetchException(e); } - StatementBuilder selectBuilder = new StatementBuilder(mRepository); + SQLStatementBuilder selectBuilder = new SQLStatementBuilder(mRepository); selectBuilder.append("SELECT "); // Don't bother using a table alias for one table. With just one table, @@ -358,7 +355,7 @@ class JDBCStorage extends StandardQueryFactory selectBuilder.append(" FROM"); - StatementBuilder fromWhereBuilder = new StatementBuilder(mRepository); + SQLStatementBuilder fromWhereBuilder = new SQLStatementBuilder(mRepository); fromWhereBuilder.append(" FROM"); if (alias == null) { @@ -840,585 +837,4 @@ class JDBCStorage extends StandardQueryFactory 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 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(); - } - - /** - * 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 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 extends Visitor { - private final JDBCRepository mRepository; - private final TableAliasGenerator mAliasGenerator; - private final JoinNode mRootJoinNode; - - JoinNodeBuilder(JDBCRepository repository, - JDBCStorableInfo 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 filter, Object param) { - try { - visit(filter); - return null; - } catch (RepositoryException e) { - throw new UndeclaredThrowableException(e); - } - } - - private void visit(PropertyFilter filter) throws RepositoryException { - ChainedProperty chained = filter.getChainedProperty(); - mRootJoinNode.addJoin(mRepository, chained, mAliasGenerator); - } - - /** - * @throws UndeclaredThrowableException wraps a RepositoryException - * since RepositoryException cannot be thrown directly - */ - @Override - public Object visit(ExistsFilter filter, Object param) { - try { - visit(filter); - return null; - } catch (RepositoryException e) { - throw new UndeclaredThrowableException(e); - } - } - - private void visit(ExistsFilter filter) throws RepositoryException { - mRootJoinNode.aliasIsRequired(); - ChainedProperty chained = filter.getChainedProperty(); - mRootJoinNode.addJoin(mRepository, chained, mAliasGenerator); - } - } - - private static class StatementBuilder { - private final JDBCRepository mRepository; - - private List> mStatements; - private StringBuilder mLiteralBuilder; - - StatementBuilder(JDBCRepository repository) { - mRepository = repository; - mStatements = new ArrayList>(); - mLiteralBuilder = new StringBuilder(); - } - - public SQLStatement build() { - if (mStatements.size() == 0 || mLiteralBuilder.length() > 0) { - mStatements.add(new LiteralStatement(mLiteralBuilder.toString())); - mLiteralBuilder.setLength(0); - } - if (mStatements.size() == 1) { - return mStatements.get(0); - } else { - return new CompositeStatement(mStatements); - } - } - - public void append(char c) { - mLiteralBuilder.append(c); - } - - public void append(String str) { - mLiteralBuilder.append(str); - } - - public void append(LiteralStatement statement) { - append(statement.toString()); - } - - public void append(SQLStatement statement) { - if (statement instanceof LiteralStatement) { - append((LiteralStatement) statement); - } else { - mStatements.add(new LiteralStatement(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 - extends Visitor - { - private final StatementBuilder mStatementBuilder; - private final JoinNode mJoinNode; - private final TableAliasGenerator mAliasGenerator; - - private List> mPropertyFilters; - private List mPropertyFilterNullable; - - /** - * @param aliasGenerator used for supporting "EXISTS" filter - */ - WhereBuilder(StatementBuilder statementBuilder, JoinNode jn, - TableAliasGenerator aliasGenerator) - { - mStatementBuilder = statementBuilder; - mJoinNode = jn; - mAliasGenerator = aliasGenerator; - mPropertyFilters = new ArrayList>(); - mPropertyFilterNullable = new ArrayList(); - } - - @SuppressWarnings("unchecked") - public PropertyFilter[] getPropertyFilters() { - return mPropertyFilters.toArray(new PropertyFilter[mPropertyFilters.size()]); - } - - public boolean[] getPropertyFilterNullable() { - boolean[] array = new boolean[mPropertyFilterNullable.size()]; - for (int i=0; i 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 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 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 filter, Object param) { - if (filter.isNotExists()) { - mStatementBuilder.append("NOT "); - } - mStatementBuilder.append("EXISTS (SELECT * FROM"); - - ChainedProperty 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 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 filter, Object param) { - mStatementBuilder.append("1=0"); - return null; - } - - private void addBindParameter(PropertyFilter 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(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 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(); + } + + /** + * 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 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 extends Visitor { + private final JDBCRepository mRepository; + private final TableAliasGenerator mAliasGenerator; + private final JoinNode mRootJoinNode; + + JoinNodeBuilder(JDBCRepository repository, + JDBCStorableInfo 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 filter, Object param) { + try { + visit(filter); + return null; + } catch (RepositoryException e) { + throw new UndeclaredThrowableException(e); + } + } + + private void visit(PropertyFilter filter) throws RepositoryException { + ChainedProperty chained = filter.getChainedProperty(); + mRootJoinNode.addJoin(mRepository, chained, mAliasGenerator); + } + + /** + * @throws UndeclaredThrowableException wraps a RepositoryException + * since RepositoryException cannot be thrown directly + */ + @Override + public Object visit(ExistsFilter filter, Object param) { + try { + visit(filter); + return null; + } catch (RepositoryException e) { + throw new UndeclaredThrowableException(e); + } + } + + private void visit(ExistsFilter filter) throws RepositoryException { + mRootJoinNode.aliasIsRequired(); + ChainedProperty 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 { 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 { + private final JDBCRepository mRepository; + + private List> mStatements; + private StringBuilder mLiteralBuilder; + + SQLStatementBuilder(JDBCRepository repository) { + mRepository = repository; + mStatements = new ArrayList>(); + mLiteralBuilder = new StringBuilder(); + } + + public SQLStatement build() { + if (mStatements.size() == 0 || mLiteralBuilder.length() > 0) { + mStatements.add(new LiteralStatement(mLiteralBuilder.toString())); + mLiteralBuilder.setLength(0); + } + if (mStatements.size() == 1) { + return mStatements.get(0); + } else { + return new CompositeStatement(mStatements); + } + } + + public void append(char c) { + mLiteralBuilder.append(c); + } + + public void append(String str) { + mLiteralBuilder.append(str); + } + + public void append(LiteralStatement statement) { + append(statement.toString()); + } + + public void append(SQLStatement statement) { + if (statement instanceof LiteralStatement) { + append((LiteralStatement) statement); + } else { + mStatements.add(new LiteralStatement(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 extends Visitor { + private final SQLStatementBuilder mStatementBuilder; + private final JoinNode mJoinNode; + private final TableAliasGenerator mAliasGenerator; + + private List> mPropertyFilters; + private List mPropertyFilterNullable; + + /** + * @param aliasGenerator used for supporting "EXISTS" filter + */ + WhereBuilder(SQLStatementBuilder statementBuilder, JoinNode jn, + TableAliasGenerator aliasGenerator) + { + mStatementBuilder = statementBuilder; + mJoinNode = jn; + mAliasGenerator = aliasGenerator; + mPropertyFilters = new ArrayList>(); + mPropertyFilterNullable = new ArrayList(); + } + + @SuppressWarnings("unchecked") + public PropertyFilter[] getPropertyFilters() { + return mPropertyFilters.toArray(new PropertyFilter[mPropertyFilters.size()]); + } + + public boolean[] getPropertyFilterNullable() { + boolean[] array = new boolean[mPropertyFilterNullable.size()]; + for (int i=0; i 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 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 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 filter, Object param) { + if (filter.isNotExists()) { + mStatementBuilder.append("NOT "); + } + mStatementBuilder.append("EXISTS (SELECT * FROM"); + + ChainedProperty 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 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 filter, Object param) { + mStatementBuilder.append("1=0"); + return null; + } + + private void addBindParameter(PropertyFilter 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(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(); + } + } +} -- cgit v1.2.3