/* * Copyright 2007-2012 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 statementBuilder destination for WHERE statement * @param jn pass null if no alias is to be used * @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(); } public void append(Filter filter) throws FetchException { mStatementBuilder.append(" WHERE "); FetchException e = filter.accept(this, null); if (e != null) { throw e; } } /** * Generate SQL of the form "WHERE EXISTS (SELECT * FROM ...)" This is * necessary for DELETE statements which operate against joined tables. */ public void appendExists(Filter filter) throws FetchException { mStatementBuilder.append(" WHERE "); mStatementBuilder.append("EXISTS (SELECT * FROM"); JDBCStorableInfo info; final JDBCRepository repo = mStatementBuilder.getRepository(); try { info = repo.examineStorable(filter.getStorableType()); } catch (RepositoryException e) { throw repo.toFetchException(e); } JoinNode jn; try { JoinNodeBuilder jnb = new JoinNodeBuilder(repo, info, mAliasGenerator); filter.accept(jnb, null); jn = jnb.getRootJoinNode(); } catch (UndeclaredThrowableException e) { throw repo.toFetchException(e); } jn.appendFullJoinTo(mStatementBuilder); mStatementBuilder.append(" WHERE "); { int i = 0; for (JDBCStorableProperty property : info.getPrimaryKeyProperties().values()) { if (i > 0) { mStatementBuilder.append(" AND "); } mStatementBuilder.append(mJoinNode.getAlias()); mStatementBuilder.append('.'); mStatementBuilder.append(property.getColumnName()); mStatementBuilder.append('='); mStatementBuilder.append(jn.getAlias()); mStatementBuilder.append('.'); mStatementBuilder.append(property.getColumnName()); i++; } } mStatementBuilder.append(" AND ("); WhereBuilder wb = new WhereBuilder(mStatementBuilder, jn, mAliasGenerator); FetchException e = filter.accept(wb, null); if (e != null) { throw e; } // Transfer property filters so that the values are extracted properly. mPropertyFilters.addAll(wb.mPropertyFilters); mPropertyFilterNullable.addAll(wb.mPropertyFilterNullable); mStatementBuilder.append(')'); mStatementBuilder.append(')'); } @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 "); { String alias = mJoinNode.findAliasFor(chained); int count = oneToMany.getJoinElementCount(); for (int i=0; i 0) { mStatementBuilder.append(" AND "); } mStatementBuilder.append(alias); mStatementBuilder.append('.'); mStatementBuilder.append(oneToMany.getInternalJoinElement(i).getColumnName()); mStatementBuilder.append('='); mStatementBuilder.append(oneToManyNode.getAlias()); 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(')'); // Transfer property filters from sub-builder as joined from exists filter. int size = wb.mPropertyFilters.size(); for (int i=0; i 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(); } } }