| /* |
| * 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.io.Serializable; |
| import java.util.Date; |
| |
| import org.apache.openjpa.jdbc.meta.ClassMapping; |
| import org.apache.openjpa.jdbc.meta.Discriminator; |
| import org.apache.openjpa.jdbc.meta.strats.NoneDiscriminatorStrategy; |
| import org.apache.openjpa.jdbc.meta.strats.VerticalClassStrategy; |
| import org.apache.openjpa.jdbc.sql.DBDictionary; |
| import org.apache.openjpa.jdbc.sql.Raw; |
| import org.apache.openjpa.kernel.exps.AggregateListener; |
| import org.apache.openjpa.kernel.exps.Arguments; |
| import org.apache.openjpa.kernel.exps.Expression; |
| import org.apache.openjpa.kernel.exps.ExpressionFactory; |
| import org.apache.openjpa.kernel.exps.FilterListener; |
| import org.apache.openjpa.kernel.exps.Literal; |
| import org.apache.openjpa.kernel.exps.Parameter; |
| import org.apache.openjpa.kernel.exps.Path; |
| import org.apache.openjpa.kernel.exps.Subquery; |
| import org.apache.openjpa.kernel.exps.Value; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.meta.ClassMetaData; |
| import org.apache.openjpa.util.UserException; |
| |
| /** |
| * Expression factory implementation that can be used to execute queries |
| * via SQL. |
| * |
| * @author Abe White |
| */ |
| public class JDBCExpressionFactory |
| implements ExpressionFactory, Serializable { |
| |
| |
| private static final long serialVersionUID = 1L; |
| |
| private static final Val NULL = new Null(); |
| |
| private static final Localizer _loc = Localizer.forPackage(JDBCExpressionFactory.class); |
| |
| private final ClassMapping _type; |
| private final SelectConstructor _cons = new SelectConstructor(); |
| private int _getMapValueAlias = 0; |
| |
| private boolean _isBooleanLiteralAsNumeric = true; |
| |
| /** |
| * Constructor. Supply the type we're querying against. |
| */ |
| public JDBCExpressionFactory(ClassMapping type) { |
| _type = type; |
| } |
| |
| public void setBooleanLiteralAsNumeric(boolean isBooleanLiteralAsNumeric) { |
| _isBooleanLiteralAsNumeric = isBooleanLiteralAsNumeric; |
| } |
| |
| /** |
| * Use to create SQL select. |
| */ |
| public SelectConstructor getSelectConstructor() { |
| return _cons; |
| } |
| |
| @Override |
| public Expression emptyExpression() { |
| return new EmptyExpression(); |
| } |
| |
| @Override |
| public Expression asExpression(Value v) { |
| return equal(v, newLiteral(Boolean.TRUE, Literal.TYPE_BOOLEAN)); |
| } |
| |
| @Override |
| public Expression equal(Value v1, Value v2) { |
| // if we're comparing an unaccessed bound variable, like in: |
| // coll.contains (var) && var == x, then translate into: |
| // coll.contains (x) |
| if (v1 instanceof PCPath && ((PCPath) v1).isUnaccessedVariable()) |
| return contains(v1, v2); |
| if (v2 instanceof PCPath && ((PCPath) v2).isUnaccessedVariable()) |
| return contains(v2, v1); |
| if (v1 instanceof Type || v2 instanceof Type) { |
| Value val = v1 instanceof Type ? v1 : v2; |
| verifyTypeOperation(val, null, false); |
| return new EqualTypeExpression((Val) v1, (Val) v2); |
| } |
| return new EqualExpression((Val) v1, (Val) v2); |
| } |
| |
| private void verifyTypeOperation(Value val, Value param, boolean isNotEqual) { |
| if (val.getPath() == null) |
| return; |
| PCPath path = (PCPath) val.getPath(); |
| Discriminator disc = ((Type) val).getDiscriminator(); |
| if (disc == null || !(val.getMetaData().getPCSuperclass() != null || |
| val.getMetaData().getPCSubclasses().length > 0)) |
| throw new UserException(_loc. |
| get("invalid-type-argument", path.last() != null ? path.getPCPathString() : path.getSchemaAlias())); |
| |
| if (disc.getColumns().length == 0) { |
| if (disc.getStrategy() instanceof NoneDiscriminatorStrategy) { |
| // limited support for table per class inheritance hierarchy |
| if (path.last() != null) |
| throw new UserException(_loc. |
| get("type-argument-unsupported", path.last().getName())); |
| if (isNotEqual) { |
| if (param != null && param instanceof Null) |
| throw new UserException(_loc. |
| get("type-in-expression-unsupported", path.getSchemaAlias())); |
| else |
| throw new UserException(_loc. |
| get("type-not-equal-unsupported", path.getSchemaAlias())); |
| } |
| } |
| if (param != null && param instanceof CollectionParam) |
| throw new UserException(_loc. |
| get("collection-param-unsupported")); |
| } |
| } |
| |
| @Override |
| public Expression notEqual(Value v1, Value v2) { |
| if (v1 instanceof Type || v2 instanceof Type) { |
| Value val = v1 instanceof Type ? v1 : v2; |
| Value param = val == v1 ? (v2 instanceof Null ? v2 : null) : (v1 instanceof Null ? v1 : null); |
| verifyTypeOperation(val, param, true); |
| return new NotEqualTypeExpression((Val) v1, (Val) v2); |
| } |
| return new NotEqualExpression((Val) v1, (Val) v2); |
| } |
| |
| @Override |
| public Expression lessThan(Value v1, Value v2) { |
| return new CompareExpression((Val) v1, (Val) v2, |
| CompareExpression.LESS); |
| } |
| |
| @Override |
| public Expression greaterThan(Value v1, Value v2) { |
| return new CompareExpression((Val) v1, (Val) v2, |
| CompareExpression.GREATER); |
| } |
| |
| @Override |
| public Expression lessThanEqual(Value v1, Value v2) { |
| return new CompareExpression((Val) v1, (Val) v2, |
| CompareExpression.LESS_EQUAL); |
| } |
| |
| @Override |
| public Expression greaterThanEqual(Value v1, Value v2) { |
| return new CompareExpression((Val) v1, (Val) v2, |
| CompareExpression.GREATER_EQUAL); |
| } |
| |
| @Override |
| public Expression isEmpty(Value val) { |
| return new IsEmptyExpression((Val) val); |
| } |
| |
| @Override |
| public Expression isNotEmpty(Value val) { |
| return new IsNotEmptyExpression((Val) val); |
| } |
| |
| @Override |
| public Expression contains(Value map, Value arg) { |
| if (map instanceof Const) { |
| if (arg instanceof Type) { |
| // limited support for table per class inheritance |
| verifyTypeOperation(arg, map, false); |
| if (((ClassMapping) arg.getMetaData()).getDiscriminator().getColumns().length == 0) |
| return new EqualTypeExpression((Val) arg, (Val) map); |
| } |
| |
| return new InExpression((Val) arg, (Const) map); |
| } |
| if (map instanceof SubQ) |
| return new InSubQExpression((Val) arg, (SubQ) map); |
| return new ContainsExpression((Val) map, (Val) arg); |
| } |
| |
| @Override |
| public Expression containsKey(Value map, Value arg) { |
| if (map instanceof Const) |
| return new InKeyExpression((Val) arg, (Const) map); |
| return new ContainsKeyExpression((Val) map, (Val) arg); |
| } |
| |
| @Override |
| public Expression containsValue(Value map, Value arg) { |
| if (map instanceof Const) |
| return new InValueExpression((Val) arg, (Const) map); |
| return new ContainsExpression((Val) map, (Val) arg); |
| } |
| |
| @Override |
| public Expression isInstance(Value val, Class c) { |
| if (val instanceof Const) |
| return new ConstInstanceofExpression((Const) val, c); |
| return new InstanceofExpression((PCPath) val, c); |
| } |
| |
| @Override |
| public Expression and(Expression exp1, Expression exp2) { |
| if (exp1 instanceof BindVariableExpression) |
| return new BindVariableAndExpression((BindVariableExpression) exp1, |
| (Exp) exp2); |
| if (exp2 instanceof BindVariableExpression) |
| return new BindVariableAndExpression((BindVariableExpression) exp2, |
| (Exp) exp1); |
| return new AndExpression((Exp) exp1, (Exp) exp2); |
| } |
| |
| @Override |
| public Expression or(Expression exp1, Expression exp2) { |
| return new OrExpression((Exp) exp1, (Exp) exp2); |
| } |
| |
| @Override |
| public Expression not(Expression exp) { |
| if (!(exp instanceof IsNotEmptyExpression) && |
| !(exp instanceof InSubQExpression) && |
| HasContainsExpressionVisitor.hasContains(exp)) |
| return new NotContainsExpression((Exp) exp); |
| return new NotExpression((Exp) exp); |
| } |
| |
| @Override |
| public Expression bindVariable(Value var, Value val) { |
| // handle the strange case of using a constant path to bind a |
| // variable; in these cases the variable acts like an unbound |
| // variable that we limit by using an IN clause on the constant |
| // value collection |
| if (val instanceof Const) { |
| PCPath path = new PCPath(_type, (Variable) var); |
| path.setMetaData(var.getMetaData()); |
| return new InExpression(path, (Const) val); |
| } |
| return new BindVariableExpression((Variable) var, (PCPath) val, false); |
| } |
| |
| @Override |
| public Expression bindKeyVariable(Value var, Value val) { |
| // handle the strange case of using a constant path to bind a |
| // variable; in these cases the variable acts like an unbound |
| // variable that we limit by using an IN clause on the constant |
| // value collection |
| if (val instanceof Const) { |
| PCPath path = new PCPath(_type, (Variable) var); |
| path.setMetaData(var.getMetaData()); |
| return new InKeyExpression(path, (Const) val); |
| } |
| return new BindVariableExpression((Variable) var, (PCPath) val, true); |
| } |
| |
| @Override |
| public Expression bindValueVariable(Value var, Value val) { |
| return bindVariable(var, val); |
| } |
| |
| @Override |
| public Expression startsWith(Value v1, Value v2) { |
| return new StartsWithExpression((Val) v1, (Val) v2); |
| } |
| |
| @Override |
| public Expression endsWith(Value v1, Value v2) { |
| return new EndsWithExpression((Val) v1, (Val) v2); |
| } |
| |
| @Override |
| public Expression notMatches(Value v1, Value v2, |
| String single, String multi, String esc) { |
| return not(matches(v1, v2, single, multi, esc)); |
| } |
| |
| @Override |
| public Expression matches(Value v1, Value v2, |
| String single, String multi, String esc) { |
| if (!(v2 instanceof Const)) |
| throw new UserException(_loc.get("const-only", "matches")); |
| if (esc == null && _type.getMappingRepository().getDBDictionary().requiresSearchStringEscapeForLike) { |
| esc = _type.getMappingRepository().getDBDictionary().searchStringEscape; |
| } |
| return new MatchesExpression((Val) v1, (Const) v2, single, multi, esc); |
| } |
| |
| @Override |
| public Subquery newSubquery(ClassMetaData candidate, boolean subs, |
| String alias) { |
| DBDictionary dict = _type.getMappingRepository().getDBDictionary(); |
| dict.assertSupport(dict.supportsSubselect, "SupportsSubselect"); |
| return new SubQ((ClassMapping) candidate, subs, alias); |
| } |
| |
| @Override |
| public Path newPath() { |
| return new PCPath(_type); |
| } |
| |
| @Override |
| public Path newPath(Value val) { |
| if (val instanceof Const) |
| return new ConstPath((Const) val); |
| if (val instanceof SubQ) |
| return new PCPath((SubQ) val); |
| return new PCPath(_type, (Variable) val); |
| } |
| |
| @Override |
| public Literal newLiteral(Object val, int ptype) { |
| return new Lit(val, ptype); |
| } |
| |
| @Override |
| public Literal newTypeLiteral(Object val, int ptype) { |
| return new TypeLit(val, ptype); |
| } |
| |
| @Override |
| public Value getThis() { |
| return new PCPath(_type); |
| } |
| |
| @Override |
| public Value getNull() { |
| return NULL; |
| } |
| |
| @Override |
| public <T extends Date> Value getCurrentDate(Class<T> dateType) { |
| return new CurrentDate(dateType); |
| } |
| |
| @Override |
| public <T extends Date> Value getCurrentTime(Class<T> dateType) { |
| return new CurrentDate(dateType); |
| } |
| |
| @Override |
| public <T extends Date> Value getCurrentTimestamp(Class<T> dateType) { |
| return new CurrentDate(dateType); |
| } |
| |
| @Override |
| public Parameter newParameter(Object name, Class type) { |
| return new Param(name, type); |
| } |
| |
| @Override |
| public Parameter newCollectionValuedParameter(Object key, Class type) { |
| return new CollectionParam(key, type); |
| } |
| |
| @Override |
| public Value newExtension(FilterListener listener, Value target, |
| Value arg) { |
| return new Extension((JDBCFilterListener) listener, |
| (Val) target, (Val) arg, _type); |
| } |
| |
| @Override |
| public Value newAggregate(AggregateListener listener, Value arg) { |
| return new Aggregate((JDBCAggregateListener) listener, |
| (Val) arg, _type); |
| } |
| |
| @Override |
| public Arguments newArgumentList(Value v1, Value v2) { |
| return new Args((Val) v1, (Val) v2); |
| } |
| |
| @Override |
| public Arguments newArgumentList(Value... vs) { |
| if (vs == null) |
| return new Args(null); |
| Val[] vals = new Val[vs.length]; |
| int i = 0; |
| for (Value v : vs) { |
| vals[i++] = (Val)v; |
| } |
| return new Args(vals); |
| } |
| |
| @Override |
| public Value newUnboundVariable(String name, Class type) { |
| return new Variable(name, type); |
| } |
| |
| @Override |
| public Value newBoundVariable(String name, Class type) { |
| return newUnboundVariable(name, type); |
| } |
| |
| @Override |
| public Value cast(Value val, Class cls) { |
| val.setImplicitType(cls); |
| return val; |
| } |
| |
| @Override |
| public Value add(Value v1, Value v2) { |
| return new Math((Val) v1, (Val) v2, Math.ADD); |
| } |
| |
| @Override |
| public Value subtract(Value v1, Value v2) { |
| return new Math((Val) v1, (Val) v2, Math.SUBTRACT); |
| } |
| |
| @Override |
| public Value multiply(Value v1, Value v2) { |
| return new Math((Val) v1, (Val) v2, Math.MULTIPLY); |
| } |
| |
| @Override |
| public Value divide(Value v1, Value v2) { |
| return new Math((Val) v1, (Val) v2, Math.DIVIDE); |
| } |
| |
| @Override |
| public Value mod(Value v1, Value v2) { |
| return new Math((Val) v1, (Val) v2, Math.MOD); |
| } |
| |
| @Override |
| public Value abs(Value val) { |
| return new Abs((Val) val); |
| } |
| |
| @Override |
| public Value indexOf(Value v1, Value v2) { |
| return new IndexOf((Val) v1, (Val) v2); |
| } |
| |
| @Override |
| public Value concat(Value v1, Value v2) { |
| return new Concat((Val) v1, (Val) v2); |
| } |
| |
| @Override |
| public Value stringLength(Value str) { |
| return new StringLength((Val) str); |
| } |
| |
| @Override |
| public Value trim(Value str, Value trimChar, Boolean where) { |
| return new Trim((Val) str, (Val) trimChar, where); |
| } |
| |
| @Override |
| public Value sqrt(Value val) { |
| return new Sqrt((Val) val); |
| } |
| |
| @Override |
| public Value substring(Value v1, Value v2) { |
| return new Substring((Val) v1, (Val) v2); |
| } |
| |
| @Override |
| public Value toUpperCase(Value val) { |
| return new ToUpperCase((Val) val); |
| } |
| |
| @Override |
| public Value toLowerCase(Value val) { |
| return new ToLowerCase((Val) val); |
| } |
| |
| @Override |
| public Value avg(Value val) { |
| return new Avg((Val) val); |
| } |
| |
| @Override |
| public Value count(Value val) { |
| return new Count((Val) val); |
| } |
| |
| @Override |
| public Value distinct(Value val) { |
| return new Distinct((Val) val); |
| } |
| |
| @Override |
| public Value max(Value val) { |
| return new Max((Val) val); |
| } |
| |
| @Override |
| public Value min(Value val) { |
| return new Min((Val) val); |
| } |
| |
| @Override |
| public Value sum(Value val) { |
| return new Sum((Val) val); |
| } |
| |
| @Override |
| public Value any(Value val) { |
| return new Any((Val) val); |
| } |
| |
| @Override |
| public Value all(Value val) { |
| return new All((Val) val); |
| } |
| |
| @Override |
| public Value size(Value val) { |
| return new Size((Val) val); |
| } |
| |
| @Override |
| public Value index(Value val) { |
| ((PCPath) val).verifyIndexedField(); |
| return new Index((Val) val); |
| } |
| |
| @Override |
| public Value type(Value val) { |
| return new Type((Val) val); |
| } |
| |
| @Override |
| public Value mapEntry(Value key, Value val) { |
| return new MapEntry((Val) key, (Val) val); |
| } |
| |
| @Override |
| public Value mapKey(Value key, Value val) { |
| return new MapKey((Val) key); |
| } |
| |
| @Override |
| public Value getKey(Value val) { |
| ((PCPath) val).getKey(); |
| return val; |
| } |
| |
| @Override |
| public Value getObjectId(Value val) { |
| if (val instanceof Const) |
| return new ConstGetObjectId((Const) val); |
| return new GetObjectId((PCPath) val); |
| } |
| |
| @Override |
| public Value getMapValue(Value map, Value arg) { |
| return new GetMapValue((Val) map, (Val) arg, |
| "gmv" + _getMapValueAlias++); |
| } |
| |
| private Value getLiteralRawString(Value val) { |
| if (val instanceof Lit) { |
| Lit lit = (Lit) val; |
| StringBuilder value = new StringBuilder(); |
| int pType = lit.getParseType(); |
| if (pType == Literal.TYPE_SQ_STRING || |
| pType == Literal.TYPE_STRING) |
| value.append("'").append(lit.getValue().toString()).append("'"); |
| else if (pType == Literal.TYPE_BOOLEAN) { |
| Boolean boolVal = (Boolean)lit.getValue(); |
| if (_isBooleanLiteralAsNumeric) |
| value.append(boolVal ? "1" : "0"); |
| else |
| value.append(boolVal ? "true" : "false"); |
| } else if (pType == Literal.TYPE_ENUM) { |
| lit.setRaw(true); |
| return val; |
| } else |
| value.append(lit.getValue().toString()); |
| lit.setValue(new Raw(value.toString())); |
| return lit; |
| } |
| return val; |
| } |
| |
| @Override |
| public Value simpleCaseExpression(Value caseOperand, Expression[] exp, |
| Value val1) { |
| Exp[] exps = new Exp[exp.length]; |
| for (int i = 0; i < exp.length; i++) |
| exps[i] = (Exp) exp[i]; |
| val1 = getLiteralRawString(val1); |
| return new SimpleCaseExpression((Val) caseOperand, exps, |
| (Val) val1); |
| } |
| |
| @Override |
| public Value generalCaseExpression(Expression[] exp, |
| Value val) { |
| Exp[] exps = new Exp[exp.length]; |
| for (int i = 0; i < exp.length; i++) |
| exps[i] = (Exp) exp[i]; |
| val = getLiteralRawString(val); |
| return new GeneralCaseExpression(exps, (Val) val); |
| } |
| |
| @Override |
| public Expression whenCondition(Expression exp, Value val) { |
| val = getLiteralRawString(val); |
| return new WhenCondition((Exp) exp, (Val) val); |
| } |
| |
| @Override |
| public Expression whenScalar(Value val1, Value val2) { |
| val1 = getLiteralRawString(val1); |
| val2 = getLiteralRawString(val2); |
| return new WhenScalar((Val) val1, (Val) val2); |
| } |
| |
| @Override |
| public Value coalesceExpression(Value[] vals) { |
| Object[] values = new Val[vals.length]; |
| for (int i = 0; i < vals.length; i++) { |
| values[i] = getLiteralRawString(vals[i]); |
| } |
| return new CoalesceExpression((Val[]) values); |
| } |
| |
| @Override |
| public Value nullIfExpression(Value val1, Value val2) { |
| val1 = getLiteralRawString(val1); |
| val2 = getLiteralRawString(val2); |
| return new NullIfExpression((Val) val1, (Val) val2); |
| } |
| |
| @Override |
| public Value newFunction(String functionName, Class<?> resultType, Value... args) { |
| return new DatastoreFunction(functionName, resultType, newArgumentList(args)); |
| } |
| |
| @Override |
| public boolean isVerticalType(Value val) { |
| if (!(val instanceof Type)) |
| return false; |
| ClassMapping cm = (ClassMapping)((Type)val).getMetaData(); |
| String strat = cm.getMappingInfo().getHierarchyStrategy(); |
| if (strat != null && strat.equals(VerticalClassStrategy.ALIAS)) |
| return true; |
| return false; |
| } |
| } |