diff options
Diffstat (limited to 'src/main/java/com')
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();
 +        }
 +    }
 +}
 | 
