| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.openjpa.jdbc.kernel.exps; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.openjpa.jdbc.schema.Column; |
| import org.apache.openjpa.jdbc.sql.Joins; |
| import org.apache.openjpa.jdbc.sql.SQLBuffer; |
| import org.apache.openjpa.jdbc.sql.Select; |
| import org.apache.openjpa.kernel.exps.ExpressionVisitor; |
| import org.apache.openjpa.kernel.exps.Parameter; |
| |
| /** |
| * Tests whether a value is IN a collection. |
| * |
| * @author Abe White |
| */ |
| class InExpression |
| implements Exp { |
| |
| |
| private static final long serialVersionUID = 1L; |
| private final Val _val; |
| private final Const _const; |
| |
| /** |
| * Constructor. Supply the value to test and the constant to obtain |
| * the parameters from. |
| */ |
| public InExpression(Val val, Const constant) { |
| _val = val; |
| _const = constant; |
| } |
| |
| /** |
| * Constant collection. |
| */ |
| public Const getConstant() { |
| return _const; |
| } |
| |
| /** |
| * Contained value. |
| */ |
| public Val getValue() { |
| return _val; |
| } |
| |
| @Override |
| public ExpState initialize(Select sel, ExpContext ctx, Map contains) { |
| ExpState valueState = _val.initialize(sel, ctx, 0); |
| ExpState constantState = _const.initialize(sel, ctx, 0); |
| return new InExpState(valueState.joins, constantState, valueState); |
| } |
| |
| /** |
| * Expression state. |
| */ |
| private static class InExpState |
| extends ExpState { |
| |
| public final ExpState constantState; |
| public final ExpState valueState; |
| |
| public InExpState(Joins joins, ExpState constantState, |
| ExpState valueState) { |
| super(joins); |
| this.constantState = constantState; |
| this.valueState = valueState; |
| } |
| } |
| |
| @Override |
| public void appendTo(Select sel, ExpContext ctx, ExpState state, |
| SQLBuffer buf) { |
| InExpState istate = (InExpState) state; |
| if (_val instanceof Type) |
| _const.calculateValue(sel, ctx, istate.constantState, _val, |
| istate.valueState); |
| else |
| _const.calculateValue(sel, ctx, istate.constantState, null, null); |
| _val.calculateValue(sel, ctx, istate.valueState, null, null); |
| |
| List list = null; |
| Collection coll = getCollection(ctx, istate.constantState); |
| if (coll != null) { |
| list = new ArrayList(coll.size()); |
| for (Object o : coll) |
| list.add(_val.toDataStoreValue(sel, ctx, istate.valueState, |
| o)); |
| } |
| |
| Column[] cols = null; |
| if (_val instanceof PCPath) |
| cols = ((PCPath) _val).getColumns(istate.valueState); |
| else if (_val instanceof GetObjectId) |
| cols = ((GetObjectId) _val).getColumns(istate.valueState); |
| |
| if (list == null || list.isEmpty()) |
| buf.append("1 <> 1"); |
| else if (_val.length(sel, ctx, istate.valueState) == 1) |
| createInContains(sel, ctx, istate.valueState, buf, list, cols); |
| else |
| orContains(sel, ctx, istate.valueState, buf, list, cols); |
| sel.append(buf, state.joins); |
| } |
| |
| /** |
| * Based on the inClauseLimit of the DBDictionary, create the needed IN |
| * clauses |
| */ |
| private void createInContains(Select sel, ExpContext ctx, ExpState state, |
| SQLBuffer buf, List list, Column[] cols) { |
| |
| int inClauseLimit = ctx.store.getDBDictionary().inClauseLimit; |
| if (inClauseLimit <= 0 || list.size() <= inClauseLimit) |
| inContains(sel, ctx, state, buf, list, cols); |
| else { |
| buf.append("("); |
| for (int low = 0, high; low < list.size(); low = high) { |
| if (low > 0) |
| buf.append(" OR "); |
| high = java.lang.Math.min(low + inClauseLimit, list.size()); |
| inContains(sel, ctx, state, buf, list.subList(low, high), cols); |
| } |
| buf.append(")"); |
| } |
| } |
| |
| /** |
| * Construct an IN clause with the value of the given collection. |
| */ |
| private void inContains(Select sel, ExpContext ctx, ExpState state, |
| SQLBuffer buf, Collection coll, Column[] cols) { |
| _val.appendTo(sel, ctx, state, buf, 0); |
| buf.append(" IN ("); |
| |
| Column col = (cols != null && cols.length == 1) ? cols[0] : null; |
| for (Iterator itr = coll.iterator(); itr.hasNext();) { |
| buf.appendValue(itr.next(), col, _const instanceof Parameter |
| ? (Parameter)_const : null); |
| if (itr.hasNext()) |
| buf.append(", "); |
| } |
| buf.append(")"); |
| } |
| |
| /** |
| * If the value to test is a compound key, we can't use IN, |
| * so create a clause like '(a = b AND c = d) OR (e = f AND g = h) ...' |
| */ |
| private void orContains(Select sel, ExpContext ctx, ExpState state, |
| SQLBuffer buf, Collection coll, Column[] cols) { |
| if (coll.size() > 1) |
| buf.append("("); |
| |
| Object[] vals; |
| Column col; |
| for (Iterator itr = coll.iterator(); itr.hasNext();) { |
| vals = (Object[]) itr.next(); |
| |
| buf.append("("); |
| for (int i = 0; i < vals.length; i++) { |
| col = (cols != null && cols.length == vals.length) |
| ? cols[i] : null; |
| if (i > 0) |
| buf.append(" AND "); |
| |
| _val.appendTo(sel, ctx, state, buf, i); |
| if (vals[i] == null) |
| buf.append(" IS "); |
| else |
| buf.append(" = "); |
| buf.appendValue(vals[i], col); |
| } |
| buf.append(")"); |
| |
| if (itr.hasNext()) |
| buf.append(" OR "); |
| } |
| if (coll.size() > 1) |
| buf.append(")"); |
| } |
| |
| @Override |
| public void selectColumns(Select sel, ExpContext ctx, ExpState state, |
| boolean pks) { |
| InExpState istate = (InExpState) state; |
| _const.selectColumns(sel, ctx, istate.constantState, true); |
| _val.selectColumns(sel, ctx, istate.valueState, true); |
| } |
| |
| /** |
| * Return the collection to test for containment with. |
| */ |
| protected Collection getCollection(ExpContext ctx, ExpState state) { |
| Object val = _const.getValue(ctx, state); |
| |
| if (val != null && val.getClass().isArray()) { |
| // arrays need to re-packaged into Collections to |
| // have a single way of handling all this |
| val = Arrays.asList((Object[]) val); |
| } |
| else if (!(val instanceof Collection)) { |
| // wrap non-Collection parameters in a Collections so the query |
| // lanuage can permit varargs "in" clauses |
| val = Collections.singleton(val); |
| } |
| |
| return (Collection) val; |
| } |
| |
| @Override |
| public void acceptVisit(ExpressionVisitor visitor) { |
| visitor.enter(this); |
| _val.acceptVisit(visitor); |
| _const.acceptVisit(visitor); |
| visitor.exit(this); |
| } |
| } |