| /* |
| * 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.kernel.jpql; |
| |
| import java.io.PrintStream; |
| import java.io.Serializable; |
| import java.lang.reflect.Field; |
| import java.math.BigDecimal; |
| import java.security.AccessController; |
| import java.sql.Time; |
| import java.sql.Timestamp; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.Stack; |
| import java.util.TreeSet; |
| |
| import org.apache.openjpa.conf.Compatibility; |
| import org.apache.openjpa.conf.OpenJPAConfiguration; |
| import org.apache.openjpa.kernel.BrokerFactory; |
| import org.apache.openjpa.kernel.ExpressionStoreQuery; |
| import org.apache.openjpa.kernel.FillStrategy; |
| import org.apache.openjpa.kernel.QueryContext; |
| import org.apache.openjpa.kernel.QueryOperations; |
| import org.apache.openjpa.kernel.ResultShape; |
| import org.apache.openjpa.kernel.StoreContext; |
| import org.apache.openjpa.kernel.exps.AbstractExpressionBuilder; |
| import org.apache.openjpa.kernel.exps.Context; |
| import org.apache.openjpa.kernel.exps.Expression; |
| import org.apache.openjpa.kernel.exps.ExpressionFactory; |
| 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.QueryExpressions; |
| import org.apache.openjpa.kernel.exps.Resolver; |
| import org.apache.openjpa.kernel.exps.Subquery; |
| import org.apache.openjpa.kernel.exps.Value; |
| import org.apache.openjpa.lib.log.Log; |
| import org.apache.openjpa.lib.util.J2DoPrivHelper; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.lib.util.Localizer.Message; |
| import org.apache.openjpa.lib.util.OrderedMap; |
| import org.apache.openjpa.meta.ClassMetaData; |
| import org.apache.openjpa.meta.FieldMetaData; |
| import org.apache.openjpa.meta.JavaTypes; |
| import org.apache.openjpa.meta.MetaDataRepository; |
| import org.apache.openjpa.meta.ValueMetaData; |
| import org.apache.openjpa.util.InternalException; |
| import org.apache.openjpa.util.UserException; |
| |
| |
| /** |
| * Builder for JPQL expressions. This class takes the query parsed |
| * in {@link JPQL} and converts it to an expression tree using |
| * an {@link ExpressionFactory}. Public for unit testing purposes. |
| * |
| * @author Marc Prud'hommeaux |
| * @author Patrick Linskey |
| */ |
| public class JPQLExpressionBuilder |
| extends AbstractExpressionBuilder |
| implements JPQLTreeConstants { |
| |
| private static final int VAR_PATH = 1; |
| private static final int VAR_ERROR = 2; |
| |
| private static final Localizer _loc = Localizer.forPackage |
| (JPQLExpressionBuilder.class); |
| |
| private final Stack<Context> contexts = new Stack<>(); |
| private OrderedMap<Object, Class<?>> parameterTypes; |
| private int aliasCount = 0; |
| private boolean inAssignSubselectProjection = false; |
| private boolean hasParameterizedInExpression = false; |
| |
| /** |
| * Constructor. |
| * |
| * @param factory the expression factory to use |
| * @param query used to resolve variables, parameters, |
| * and class names used in the query |
| * @param parsedQuery the parsed query |
| */ |
| public JPQLExpressionBuilder(ExpressionFactory factory, |
| ExpressionStoreQuery query, Object parsedQuery) { |
| super(factory, query.getResolver()); |
| |
| contexts.push(new Context(parsedQuery instanceof ParsedJPQL |
| ? (ParsedJPQL) parsedQuery |
| : parsedQuery instanceof String |
| ? getParsedQuery((String) parsedQuery) |
| : null, null, null)); |
| |
| if (ctx().parsed == null) |
| throw new InternalException(parsedQuery + ""); |
| } |
| |
| @Override |
| protected Localizer getLocalizer() { |
| return _loc; |
| } |
| |
| @Override |
| protected ClassLoader getClassLoader() { |
| // we don't resolve in the context of anything but ourselves |
| return getClass().getClassLoader(); |
| } |
| |
| protected ParsedJPQL getParsedQuery() { |
| return ctx().parsed; |
| } |
| |
| protected ParsedJPQL getParsedQuery(String jpql) { |
| return new ParsedJPQL(jpql); |
| } |
| |
| private void setCandidate(ClassMetaData cmd, String schemaAlias) { |
| addAccessPath(cmd); |
| |
| if (cmd != null) |
| ctx().meta = cmd; |
| |
| if (schemaAlias != null) |
| ctx().schemaAlias = schemaAlias; |
| } |
| |
| private String nextAlias() { |
| return "jpqlalias" + (++aliasCount); |
| } |
| |
| protected ClassMetaData resolveClassMetaData(JPQLNode node) { |
| // handle looking up alias names |
| String schemaName = assertSchemaName(node); |
| ClassMetaData cmd = getClassMetaData(schemaName, false); |
| if (cmd != null) |
| return cmd; |
| |
| // we might be referencing a collection field of a subquery's parent |
| if (isPath(node)) { |
| Path path = getPath(node); |
| FieldMetaData fmd = path.last(); |
| cmd = getFieldType(fmd); |
| if (cmd == null && fmd.isElementCollection()) |
| cmd = fmd.getDefiningMetaData(); |
| return cmd; |
| } |
| |
| // now run again to throw the correct exception |
| return getClassMetaData(schemaName, true); |
| } |
| |
| private ClassMetaData getClassMetaData(String alias, boolean assertValid) { |
| ClassLoader loader = getClassLoader(); |
| MetaDataRepository repos = resolver.getConfiguration(). |
| getMetaDataRepositoryInstance(); |
| |
| // first check for the alias |
| ClassMetaData cmd = repos.getMetaData(alias, loader, false); |
| |
| if (cmd != null) |
| return cmd; |
| |
| // now check for the class name; this is not technically permitted |
| // by the JPA spec, but is required in order to be able to execute |
| // JPQL queries from other facades (like JDO) that do not have |
| // the concept of entity names or aliases |
| Class<?> c = resolver.classForName(alias, null); |
| if (c != null) |
| cmd = repos.getMetaData(c, loader, assertValid); |
| else if (assertValid) |
| cmd = repos.getMetaData(alias, loader, false); |
| |
| if (cmd == null && assertValid) { |
| String close = repos.getClosestAliasName(alias); |
| if (close != null) |
| throw parseException(EX_USER, "not-schema-name-hint", |
| new Object[]{ alias, close, repos.getAliasNames() }, null); |
| else |
| throw parseException(EX_USER, "not-schema-name", |
| new Object[]{ alias, repos.getAliasNames() }, null); |
| } |
| |
| return cmd; |
| } |
| |
| private Class<?> getCandidateType() { |
| return getCandidateMetaData().getDescribedType(); |
| } |
| |
| private ClassMetaData getCandidateMetaData() { |
| if (ctx().meta != null) |
| return ctx().meta; |
| |
| ClassMetaData cls = getCandidateMetaData(root()); |
| if (cls == null) |
| throw parseException(EX_USER, "not-schema-name", |
| new Object[]{ root() }, null); |
| |
| setCandidate(cls, null); |
| return cls; |
| } |
| |
| protected ClassMetaData getCandidateMetaData(JPQLNode node) { |
| // examing the node to find the candidate query |
| // ### this should actually be the primary SELECT instance |
| // resolved against the from variable declarations |
| JPQLNode from = node.findChildByID(JJTFROMITEM, true); |
| if (from == null) { |
| // OPENJPA-15 allow subquery without a FROMITEM |
| if (node.id == JJTSUBSELECT) { |
| from = node.findChildByID(JJTFROM, true); |
| } |
| else { |
| throw parseException(EX_USER, "no-from-clause", null, null); |
| } |
| } |
| |
| for (int i = 0; i < from.children.length; i++) { |
| JPQLNode n = from.children[i]; |
| |
| if (n.id == JJTABSTRACTSCHEMANAME) { |
| // we simply return the first abstract schema child |
| // as resolved into a class |
| ClassMetaData cmd = resolveClassMetaData(n); |
| |
| if (cmd != null) |
| return cmd; |
| |
| // not a schema: treat it as a class |
| String cls = assertSchemaName(n); |
| if (cls == null) |
| throw parseException(EX_USER, "not-schema-name", |
| new Object[]{ root() }, null); |
| |
| return getClassMetaData(cls, true); |
| } |
| // OPENJPA-15 support subquery's from clause do not start with |
| // identification_variable_declaration() |
| if (node.id == JJTSUBSELECT) { |
| if (n.id == JJTINNERJOIN) { |
| n = n.getChild(0); |
| } |
| if (n.id == JJTPATH) { |
| Path path = getPath(n); |
| FieldMetaData fmd = path.last(); |
| ClassMetaData cmd = getFieldType(fmd); |
| if (cmd == null && fmd.isElementCollection()) |
| cmd = fmd.getDefiningMetaData(); |
| if (cmd != null) { |
| return cmd; |
| } |
| else { |
| throw parseException(EX_USER, "no-alias", |
| new Object[]{ n }, null); |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| protected String currentQuery() { |
| return ctx().parsed == null || root().parser == null ? null |
| : root().parser.jpql; |
| } |
| |
| QueryExpressions getQueryExpressions() { |
| QueryExpressions exps = new QueryExpressions(); |
| exps.setContexts(contexts); |
| |
| evalQueryOperation(exps); |
| |
| Expression filter = null; |
| Expression from = ctx().from; |
| if (from == null) |
| from = evalFromClause(root().id == JJTSELECT); |
| filter = and(from, filter); |
| filter = and(evalWhereClause(), filter); |
| filter = and(evalSelectClause(exps), filter); |
| |
| exps.filter = filter == null ? factory.emptyExpression() : filter; |
| |
| evalGroupingClause(exps); |
| evalHavingClause(exps); |
| evalFetchJoins(exps); |
| evalSetClause(exps); |
| evalOrderingClauses(exps); |
| |
| if (parameterTypes != null) |
| exps.parameterTypes = parameterTypes; |
| |
| exps.accessPath = getAccessPath(); |
| exps.hasInExpression = this.hasParameterizedInExpression; |
| |
| // verify parameters are consistent. |
| validateParameters(); |
| |
| return exps; |
| } |
| |
| private Expression and(Expression e1, Expression e2) { |
| return e1 == null ? e2 : e2 == null ? e1 : factory.and(e1, e2); |
| } |
| |
| private static String assemble(JPQLNode node) { |
| return assemble(node, ".", 0); |
| } |
| |
| /** |
| * Assemble the children of the specific node by appending each |
| * child, separated by the delimiter. |
| */ |
| private static String assemble(JPQLNode node, String delimiter, int last) { |
| StringBuilder result = new StringBuilder(); |
| JPQLNode[] parts = node.children; |
| for (int i = 0; parts != null && i < parts.length - last; i++) |
| result.append(result.length() > 0 ? delimiter : ""). |
| append(parts[i].text); |
| |
| return result.toString(); |
| } |
| |
| private Expression assignSubselectProjection(JPQLNode node, |
| QueryExpressions exps) { |
| inAssignSubselectProjection = true; |
| exps.projections = new Value[1]; |
| exps.projectionClauses = new String[1]; |
| exps.projectionAliases = new String[1]; |
| |
| Value val = getValue(node); |
| exps.projections[0] = val; |
| exps.projectionClauses[0] = |
| projectionClause(node.id == JJTSCALAREXPRESSION ? |
| firstChild(node) : node); |
| inAssignSubselectProjection = false; |
| return null; |
| } |
| |
| /** |
| * Assign projections for NEW contructor in selection list. |
| * Example: SELECT NEW Person(p.name) FROM Person p WHERE ... |
| */ |
| private Expression assignProjections(JPQLNode parametersNode, |
| QueryExpressions exps, List<Value> projections, |
| List<String> projectionClauses, List<String> projectionAliases) { |
| int count = parametersNode.getChildCount(); |
| |
| Expression exp = null; |
| for (int i = 0; i < count; i++) { |
| JPQLNode parent = parametersNode.getChild(i); |
| JPQLNode node = firstChild(parent); |
| JPQLNode aliasNode = parent.children.length > 1 ? right(parent) |
| : null; |
| Value proj = getValue(node); |
| String alias = aliasNode != null ? aliasNode.text : |
| projectionClause(node.id == JJTSCALAREXPRESSION ? |
| firstChild(node) : node); |
| if (aliasNode != null) |
| proj.setAlias(alias); |
| projections.add(proj); |
| projectionClauses.add(alias); |
| projectionAliases.add(alias); |
| } |
| return exp; |
| } |
| |
| private void evalProjectionsResultShape(JPQLNode selectionsNode, |
| QueryExpressions exps, |
| List<Value> projections, |
| List<String> projectionClauses, |
| List<String> projectionAliases) { |
| int count = selectionsNode.getChildCount(); |
| Class<?> resultClass = null; |
| ResultShape<?> resultShape = null; |
| if (count > 1) { |
| // muti-selection |
| resultClass = Object[].class; |
| resultShape = new ResultShape(resultClass, new FillStrategy.Array<>(Object[].class)); |
| } |
| |
| for (int i = 0; i < count; i++) { |
| JPQLNode parent = selectionsNode.getChild(i); |
| JPQLNode node = firstChild(parent); |
| if (node.id == JJTCONSTRUCTOR) { |
| // build up the fully-qualified result class name by |
| // appending together the components of the children |
| String resultClassName = assemble(left(node)); |
| Class<?> constructor = resolver.classForName(resultClassName, null); |
| if (constructor == null) { |
| // try resolve it again using simple name |
| int n = left(node).getChildCount(); |
| String baseName = left(node).getChild(n-1).text; |
| constructor = resolver.classForName(baseName, null); |
| } |
| |
| if (constructor == null && resolver.getConfiguration().getUseTCCLinSelectNew()) { |
| try { |
| if (System.getSecurityManager() != null) { |
| constructor = AccessController.doPrivileged( |
| J2DoPrivHelper.getForNameAction(resultClassName, false, |
| AccessController.doPrivileged(J2DoPrivHelper.getContextClassLoaderAction()))); |
| } |
| else { |
| constructor = Thread.currentThread().getContextClassLoader().loadClass(resultClassName); |
| } |
| } catch (Exception e) { |
| // ignore |
| } |
| } |
| |
| if (constructor == null) |
| throw parseException(EX_USER, "no-constructor", |
| new Object[]{ resultClassName }, null); |
| |
| List<Value> terms = new ArrayList<>(); |
| List<String> aliases = new ArrayList<>(); |
| List<String> clauses = new ArrayList<>(); |
| // now assign the arguments to the select clause as the projections |
| assignProjections(right(node), exps, terms, aliases, clauses); |
| FillStrategy fill = new FillStrategy.NewInstance(constructor); |
| ResultShape<?> cons = new ResultShape(constructor, fill); |
| for (Value val : terms) { |
| Class<?> type = val.getType(); |
| cons.nest(new ResultShape(type, new FillStrategy.Assign(), type.isPrimitive())); |
| } |
| if (count == 1) { |
| resultClass = constructor; |
| resultShape = cons; |
| } |
| else |
| resultShape.nest(cons); |
| projections.addAll(terms); |
| projectionAliases.addAll(aliases); |
| projectionClauses.addAll(clauses); |
| |
| } else { |
| JPQLNode aliasNode = parent.children.length > 1 ? right(parent) |
| : null; |
| Value proj = getValue(node); |
| String alias = aliasNode != null ? aliasNode.text : |
| projectionClause(node.id == JJTSCALAREXPRESSION ? |
| firstChild(node) : node); |
| if (aliasNode != null) |
| proj.setAlias(alias); |
| projections.add(proj); |
| projectionClauses.add(alias); |
| projectionAliases.add(alias); |
| Class<?> type = proj.getType(); |
| ResultShape<?> projShape = new ResultShape(type, new FillStrategy.Assign(), type.isPrimitive()); |
| |
| if (count == 1) |
| resultShape = projShape; |
| else |
| resultShape.nest(projShape); |
| } |
| } |
| exps.shape = resultShape; |
| exps.resultClass = resultClass; |
| } |
| |
| private String projectionClause(JPQLNode node) { |
| switch (node.id) { |
| case JJTTYPE: |
| return projectionClause(firstChild(node)); |
| default: |
| return assemble(node); |
| } |
| } |
| |
| private void evalQueryOperation(QueryExpressions exps) { |
| // determine whether we want to select, delete, or update |
| if (root().id == JJTSELECT || root().id == JJTSUBSELECT) |
| exps.operation = QueryOperations.OP_SELECT; |
| else if (root().id == JJTDELETE) |
| exps.operation = QueryOperations.OP_DELETE; |
| else if (root().id == JJTUPDATE) |
| exps.operation = QueryOperations.OP_UPDATE; |
| else |
| throw parseException(EX_UNSUPPORTED, "unrecognized-operation", |
| new Object[]{ root() }, null); |
| } |
| |
| private void evalGroupingClause(QueryExpressions exps) { |
| // handle GROUP BY clauses |
| JPQLNode groupByNode = root().findChildByID(JJTGROUPBY, false); |
| |
| if (groupByNode == null) |
| return; |
| |
| int groupByCount = groupByNode.getChildCount(); |
| |
| exps.grouping = new Value[groupByCount]; |
| |
| for (int i = 0; i < groupByCount; i++) { |
| JPQLNode node = groupByNode.getChild(i); |
| Value val = getValue(node); |
| if (val instanceof Path) { |
| FieldMetaData fmd = ((Path) val).last(); |
| if (fmd != null && fmd.getValue().getTypeMetaData() != null && fmd.getValue().isEmbedded()) |
| throw parseException(EX_USER, "cant-groupby-embeddable", |
| new Object[]{ node.getChildCount() > 1 ? assemble(node) : node.text }, null); |
| } |
| exps.grouping[i] = val; |
| } |
| } |
| |
| private void evalHavingClause(QueryExpressions exps) { |
| // handle HAVING clauses |
| JPQLNode havingNode = root().findChildByID(JJTHAVING, false); |
| |
| if (havingNode == null) |
| return; |
| |
| exps.having = getExpression(onlyChild(havingNode)); |
| } |
| |
| private void evalOrderingClauses(QueryExpressions exps) { |
| // handle ORDER BY clauses |
| JPQLNode orderby = root().findChildByID(JJTORDERBY, false); |
| if (orderby != null) { |
| int ordercount = orderby.getChildCount(); |
| exps.ordering = new Value[ordercount]; |
| exps.orderingClauses = new String[ordercount]; |
| exps.orderingAliases = new String[ordercount]; |
| exps.ascending = new boolean[ordercount]; |
| for (int i = 0; i < ordercount; i++) { |
| JPQLNode node = orderby.getChild(i); |
| JPQLNode firstChild = firstChild(node); |
| exps.ordering[i] = getValue(firstChild); |
| exps.orderingClauses[i] = assemble(firstChild); |
| exps.orderingAliases[i] = firstChild.text; |
| |
| // ommission of ASC/DESC token implies ascending |
| exps.ascending[i] = node.getChildCount() <= 1 || |
| lastChild(node).id == JJTASCENDING ? true : false; |
| } |
| // check if order by select item result alias |
| for (int i = 0; i < ordercount; i++) { |
| if (exps.orderingClauses[i] != null && |
| !exps.orderingClauses[i].equals("")) |
| continue; |
| for (int j = 0; j < exps.projections.length; j++) { |
| if (exps.projectionAliases[j].equalsIgnoreCase( |
| exps.orderingAliases[i])) { |
| exps.ordering[i] = exps.projections[j]; |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| private Expression evalSelectClause(QueryExpressions exps) { |
| if (exps.operation != QueryOperations.OP_SELECT) |
| return null; |
| |
| JPQLNode selectNode = root(); |
| |
| JPQLNode selectClause = selectNode. |
| findChildByID(JJTSELECTCLAUSE, false); |
| if (selectClause != null && selectClause.hasChildID(JJTDISTINCT)) |
| exps.distinct = QueryExpressions.DISTINCT_TRUE |
| | QueryExpressions.DISTINCT_AUTO; |
| else |
| exps.distinct = QueryExpressions.DISTINCT_FALSE; |
| |
| // handle SELECT clauses |
| JPQLNode expNode = selectNode. |
| findChildByID(JJTSELECTEXPRESSIONS, true); |
| if (expNode == null) { |
| return null; |
| } |
| |
| int selectCount = expNode.getChildCount(); |
| JPQLNode selectChild = firstChild(expNode); |
| |
| if (selectClause.parent.id == JJTSUBSELECT) { |
| exps.distinct &= ~QueryExpressions.DISTINCT_AUTO; |
| return assignSubselectProjection(onlyChild(selectChild), exps); |
| } |
| // if we are selecting just one thing and that thing is the |
| // schema's alias, then do not treat it as a projection |
| if (selectCount == 1 && selectChild != null && |
| selectChild.getChildCount() == 1 && |
| onlyChild(selectChild) != null) { |
| JPQLNode child = onlyChild(selectChild); |
| if (child.id == JJTSCALAREXPRESSION) |
| child = onlyChild(child); |
| if (assertSchemaAlias().equalsIgnoreCase(child.text)) { |
| return null; |
| } |
| } |
| // JPQL does not filter relational joins for projections |
| exps.distinct &= ~QueryExpressions.DISTINCT_AUTO; |
| exps.projections = new Value[selectCount]; |
| List<Value> projections = new ArrayList<>(); |
| List<String> aliases = new ArrayList<>(); |
| List<String> clauses = new ArrayList<>(); |
| evalProjectionsResultShape(expNode, exps, projections, aliases, clauses); |
| exps.projections = projections.toArray(new Value[projections.size()]); |
| exps.projectionAliases = aliases.toArray(new String[aliases.size()]); |
| exps.projectionClauses = clauses.toArray(new String[clauses.size()]); |
| return null; |
| } |
| |
| private String assertSchemaAlias() { |
| String alias = ctx().schemaAlias; |
| |
| if (alias == null) |
| throw parseException(EX_USER, "alias-required", |
| new Object[]{ ctx().meta }, null); |
| |
| return alias; |
| } |
| |
| protected Expression evalFetchJoins(QueryExpressions exps) { |
| Expression filter = null; |
| |
| // handle JOIN FETCH |
| Set<String> joins = null; |
| Set<String> innerJoins = null; |
| |
| JPQLNode[] outers = root().findChildrenByID(JJTOUTERFETCHJOIN); |
| for (int i = 0; outers != null && i < outers.length; i++) |
| (joins == null ? joins = new TreeSet<>() : joins). |
| add(getPath(onlyChild(outers[i])).last().getFullName(false)); |
| |
| JPQLNode[] inners = root().findChildrenByID(JJTINNERFETCHJOIN); |
| for (int i = 0; inners != null && i < inners.length; i++) { |
| String path = getPath(onlyChild(inners[i])).last() |
| .getFullName(false); |
| (joins == null ? joins = new TreeSet<>() : joins).add(path); |
| (innerJoins == null |
| ? innerJoins = new TreeSet<>() |
| : innerJoins).add(path); |
| } |
| |
| if (joins != null) |
| exps.fetchPaths = joins. |
| toArray(new String[joins.size()]); |
| if (innerJoins != null) |
| exps.fetchInnerPaths = innerJoins. |
| toArray(new String[innerJoins.size()]); |
| |
| return filter; |
| } |
| |
| protected void evalSetClause(QueryExpressions exps) { |
| // handle SET field = value |
| JPQLNode[] nodes = root().findChildrenByID(JJTUPDATEITEM); |
| for (int i = 0; nodes != null && i < nodes.length; i++) { |
| Path path = getPath(firstChild(nodes[i])); |
| if (path.last().getValue().getEmbeddedMetaData() != null) |
| throw parseException(EX_USER, "cant-bulk-update-embeddable", |
| new Object[]{assemble(firstChild(nodes[i]))}, null); |
| |
| JPQLNode lastChild = lastChild(nodes[i]); |
| Value val = (lastChild.children == null) |
| ? null : getValue(onlyChild(lastChild)); |
| exps.putUpdate(path, val); |
| } |
| } |
| |
| private Expression evalWhereClause() { |
| // evaluate the WHERE clause |
| JPQLNode whereNode = root().findChildByID(JJTWHERE, false); |
| if (whereNode == null) |
| return null; |
| return (Expression) eval(whereNode); |
| } |
| |
| private Expression evalFromClause(boolean needsAlias) { |
| // build up the alias map in the FROM clause |
| JPQLNode from = root().findChildByID(JJTFROM, false); |
| if (from == null) |
| throw parseException(EX_USER, "no-from-clause", null, null); |
| return evalFromClause(from, needsAlias); |
| } |
| |
| private Expression evalFromClause(JPQLNode from, boolean needsAlias) { |
| Expression exp = null; |
| |
| for (int i = 0; i < from.children.length; i++) { |
| JPQLNode node = from.children[i]; |
| |
| if (node.id == JJTFROMITEM) |
| exp = evalFromItem(exp, node, needsAlias); |
| else if (node.id == JJTOUTERJOIN) |
| exp = addJoin(node, false, exp); |
| else if (node.id == JJTINNERJOIN) |
| exp = addJoin(node, true, exp); |
| else if (node.id == JJTINNERFETCHJOIN) |
| ; // we handle inner fetch joins in the evalFetchJoins() method |
| else if (node.id == JJTOUTERFETCHJOIN) |
| ; // we handle outer fetch joins in the evalFetchJoins() method |
| else |
| throw parseException(EX_USER, "not-schema-name", |
| new Object[]{ node }, null); |
| } |
| |
| return exp; |
| } |
| |
| private Expression getSubquery(String alias, Path path, Expression exp) { |
| Value var = getVariable(alias, true); |
| // this bind is for validateMapPath to resolve alias |
| Expression bindVar = factory.bindVariable(var, path); |
| FieldMetaData fmd = path.last(); |
| ClassMetaData candidate = getFieldType(fmd); |
| if (candidate == null && fmd.isElementCollection()) |
| candidate = fmd.getDefiningMetaData(); |
| |
| setCandidate(candidate, alias); |
| |
| Context subContext = ctx(); |
| Subquery subquery = ctx().getSubquery(); |
| if (subquery == null){ |
| subquery = factory.newSubquery(candidate, true, alias); |
| subContext.setSubquery(subquery); |
| } |
| else { |
| subquery.setSubqAlias(alias); |
| } |
| |
| Path subpath = factory.newPath(subquery); |
| subpath.setSchemaAlias(path.getCorrelationVar()); |
| subpath.setMetaData(candidate); |
| subquery.setMetaData(candidate); |
| if (fmd.isElementCollection()) |
| exp = and(exp, bindVar); |
| else |
| exp = and(exp, factory.equal(path, subpath)); |
| |
| return exp; |
| } |
| |
| /** |
| * Adds a join condition to the given expression. |
| * |
| * @param node the node to check |
| * @param inner whether or not the join should be an inner join |
| * @param exp an existing expression to AND, or null if none |
| * @return the Expression with the join condition added |
| */ |
| private Expression addJoin(JPQLNode node, boolean inner, Expression exp) { |
| // the type will be the declared type for the field |
| JPQLNode firstChild = firstChild(node); |
| Path path = null; |
| if (firstChild.id == JJTQUALIFIEDPATH) |
| path = getQualifiedPath(firstChild); |
| else |
| path = getPath(firstChild, false, inner); |
| |
| JPQLNode alias = node.getChildCount() >= 2 ? right(node) : null; |
| // OPENJPA-15 support subquery's from clause do not start with |
| // identification_variable_declaration() |
| if (inner && ctx().getParent() != null && ctx().schemaAlias == null) { |
| return getSubquery(alias.text, path, exp); |
| } |
| |
| return addJoin(path, alias, exp); |
| } |
| |
| private Expression addJoin(Path path, JPQLNode aliasNode, |
| Expression exp) { |
| FieldMetaData fmd = path.last(); |
| |
| if (fmd == null) |
| throw parseException(EX_USER, "path-no-meta", |
| new Object[]{ path, null }, null); |
| |
| String alias = aliasNode != null ? aliasNode.text : nextAlias(); |
| |
| Value var = getVariable(alias, true); |
| var.setMetaData(getFieldType(fmd)); |
| |
| Expression join = null; |
| |
| // if the variable is already bound, get the var's value and |
| // do a regular contains with that |
| boolean bound = isBound(var); |
| if (bound) { |
| var = getValue(aliasNode, VAR_PATH); |
| } else { |
| bind(var); |
| join = and(join, factory.bindVariable(var, path)); |
| } |
| |
| if (!fmd.isTypePC()) // multi-valued relation |
| { |
| if (bound) |
| join = and(join, factory.contains(path, var)); |
| |
| setImplicitContainsTypes(path, var, CONTAINS_TYPE_ELEMENT); |
| } |
| |
| return and(exp, join); |
| } |
| |
| private Expression evalFromItem(Expression exp, JPQLNode node, |
| boolean needsAlias) { |
| ClassMetaData cmd = resolveClassMetaData(firstChild(node)); |
| |
| String alias = null; |
| |
| if (node.getChildCount() < 2) { |
| if (needsAlias) |
| throw parseException(EX_USER, "alias-required", |
| new Object[]{ cmd }, null); |
| } else { |
| alias = right(node).text; |
| JPQLNode left = left(node); |
| addSchemaToContext(alias, cmd); |
| |
| // check to see if the we are referring to a path in the from |
| // clause, since we might be in a subquery against a collection |
| if (isPath(left)) { |
| Path path = getPath(left); |
| return getSubquery(alias, path, exp); |
| } else { |
| // we have an alias: bind it as a variable |
| Value var = getVariable(alias, true); |
| var.setMetaData(cmd); |
| bind(var); |
| } |
| } |
| |
| // ### we assign the first FROMITEM instance we see as |
| // the global candidate, which is incorrect: we should |
| // instead be mapping this to the SELECTITEM to see |
| // which is the desired candidate |
| if (ctx().schemaAlias == null) |
| setCandidate(cmd, alias); |
| else |
| addAccessPath(cmd); |
| |
| return exp; |
| } |
| |
| @Override |
| protected boolean isDeclaredVariable(String name) { |
| // JPQL doesn't support declaring variables |
| return false; |
| } |
| |
| /** |
| * Check to see if the specific node is a path (vs. a schema name) |
| */ |
| boolean isPath(JPQLNode node) { |
| if (node.getChildCount() < 2) |
| return false; |
| |
| final String name = firstChild(node).text; |
| if (name == null) |
| return false; |
| |
| // handle the case where the class name is the alias |
| // for the candidate (we don't use variables for this) |
| if (getMetaDataForAlias(name) != null) |
| return true; |
| |
| if (!isSeenVariable(name)) |
| return false; |
| |
| final Value var = getVariable(name, false); |
| |
| if (var != null) |
| return isBound(var); |
| |
| return false; |
| } |
| |
| private static ClassMetaData getFieldType(FieldMetaData fmd) { |
| if (fmd == null) |
| return null; |
| |
| ClassMetaData cmd = null; |
| ValueMetaData vmd; |
| |
| if ((vmd = fmd.getElement()) != null) |
| cmd = vmd.getDeclaredTypeMetaData(); |
| else if ((vmd = fmd.getKey()) != null) |
| cmd = vmd.getDeclaredTypeMetaData(); |
| else if ((vmd = fmd.getValue()) != null) |
| cmd = vmd.getDeclaredTypeMetaData(); |
| |
| if (cmd == null || cmd.getDescribedType() == Object.class) |
| cmd = fmd.getDeclaredTypeMetaData(); |
| |
| return cmd; |
| } |
| |
| /** |
| * Identification variables in JPQL are case insensitive, so lower-case |
| * all variables we are going to bind. |
| */ |
| @Override |
| protected Value getVariable(String id, boolean bind) { |
| if (id == null) |
| return null; |
| |
| if (bind && getDefinedVariable(id) == null) |
| return createVariable(id, bind); |
| |
| return super.getVariable(id.toLowerCase(), bind); |
| } |
| |
| protected Value getDefinedVariable(String id) { |
| return ctx().getVariable(id); |
| } |
| |
| @Override |
| protected boolean isSeenVariable(String var) { |
| Context c = ctx().findContext(var); |
| if (c != null) |
| return true; |
| return false; |
| } |
| |
| /** |
| * Returns the class name using the children of the JPQLNode. |
| */ |
| private String assertSchemaName(JPQLNode node) { |
| if (node.id != JJTABSTRACTSCHEMANAME) |
| throw parseException(EX_USER, "not-identifer", |
| new Object[]{ node }, null); |
| |
| return assemble(node); |
| } |
| |
| private void checkEmbeddable(Value val) { |
| checkEmbeddable(val, currentQuery()); |
| } |
| |
| public static void checkEmbeddable(Value val, String currentQuery) { |
| Path path = val instanceof Path ? (Path) val : null; |
| if (path == null) |
| return; |
| |
| FieldMetaData fmd = path.last(); |
| if (fmd == null) |
| return; |
| |
| ValueMetaData vm = fmd.isElementCollection() ? fmd.getElement() |
| : fmd.getValue(); |
| if (vm.getEmbeddedMetaData() != null) { |
| //throw parseException(EX_USER, "bad-predicate", |
| // new Object[]{ currentQuery() }, null); |
| String argStr = _loc.get("bad-predicate", |
| new Object[] {fmd.getName()}).getMessage(); |
| Message msg = _loc.get("parse-error", argStr, currentQuery); |
| throw new UserException(msg, null); |
| } |
| } |
| |
| /** |
| * Recursive helper method to evaluate the given node. |
| */ |
| private Object eval(JPQLNode node) { |
| Value val1 = null; |
| Value val2 = null; |
| Value val3 = null; |
| |
| boolean not = node.not; |
| |
| switch (node.id) { |
| case JJTSCALAREXPRESSION: |
| return eval(onlyChild(node)); |
| |
| case JJTTYPE: |
| return getType(onlyChild(node)); |
| |
| case JJTTYPELITERAL: |
| return getTypeLiteral(node); |
| |
| case JJTCLASSNAME: |
| return getPathOrConstant(node); |
| |
| case JJTCASE: |
| return eval(onlyChild(node)); |
| |
| case JJTSIMPLECASE: |
| return getSimpleCaseExpression(node); |
| |
| case JJTGENERALCASE: |
| return getGeneralCaseExpression(node); |
| |
| case JJTWHEN: |
| return getWhenCondition(node); |
| |
| case JJTWHENSCALAR: |
| return getWhenScalar(node); |
| |
| case JJTCOALESCE: |
| return getCoalesceExpression(node); |
| |
| case JJTNULLIF: |
| return getNullIfExpression(node); |
| |
| case JJTWHERE: // top-level WHERE clause |
| return getExpression(onlyChild(node)); |
| |
| case JJTBOOLEANLITERAL: |
| return factory.newLiteral("true".equalsIgnoreCase |
| (node.text) ? Boolean.TRUE : Boolean.FALSE, |
| Literal.TYPE_BOOLEAN); |
| |
| case JJTINTEGERLITERAL: |
| // use BigDecimal because it can handle parsing exponents |
| BigDecimal intlit = new BigDecimal |
| (node.text.endsWith("l") || node.text.endsWith("L") |
| ? node.text.substring(0, node.text.length() - 1) |
| : node.text). |
| multiply(new BigDecimal(negative(node))); |
| return factory.newLiteral(intlit.longValue(), |
| Literal.TYPE_NUMBER); |
| |
| case JJTDECIMALLITERAL: |
| BigDecimal declit = new BigDecimal |
| (node.text.endsWith("d") || node.text.endsWith("D") || |
| node.text.endsWith("f") || node.text.endsWith("F") |
| ? node.text.substring(0, node.text.length() - 1) |
| : node.text). |
| multiply(new BigDecimal(negative(node))); |
| return factory.newLiteral(declit, Literal.TYPE_NUMBER); |
| |
| case JJTSTRINGLITERAL: |
| case JJTTRIMCHARACTER: |
| case JJTESCAPECHARACTER: |
| return factory.newLiteral(trimQuotes(node.text), |
| Literal.TYPE_SQ_STRING); |
| |
| case JJTSTRINGLITERAL2: |
| return factory.newLiteral(trimDoubleQuotes(node.text), |
| Literal.TYPE_SQ_STRING); |
| |
| case JJTPATTERNVALUE: |
| return eval(firstChild(node)); |
| |
| case JJTNAMEDINPUTPARAMETER: |
| return getParameter(onlyChild(node).text, false, false); |
| |
| case JJTPOSITIONALINPUTPARAMETER: |
| return getParameter(node.text, true, false); |
| |
| case JJTCOLLECTIONPARAMETER: |
| JPQLNode child = onlyChild(node); |
| boolean positional = child.id == JJTPOSITIONALINPUTPARAMETER; |
| if (!positional) |
| child = onlyChild(child); |
| return getParameter(child.text, |
| positional, true); |
| |
| case JJTOR: // x OR y |
| return factory.or(getExpression(left(node)), |
| getExpression(right(node))); |
| |
| case JJTAND: // x AND y |
| return and(getExpression(left(node)), |
| getExpression(right(node))); |
| |
| case JJTEQUALS: // x = y |
| val1 = getValue(left(node)); |
| val2 = getValue(right(node)); |
| setImplicitTypes(val1, val2, null); |
| return factory.equal(val1, val2); |
| |
| case JJTNOTEQUALS: // x <> y |
| val1 = getValue(left(node)); |
| val2 = getValue(right(node)); |
| setImplicitTypes(val1, val2, null); |
| return factory.notEqual(val1, val2); |
| |
| case JJTLESSTHAN: // x < y |
| val1 = getValue(left(node)); |
| val2 = getValue(right(node)); |
| setImplicitTypes(val1, val2, null); |
| return factory.lessThan(val1, val2); |
| |
| case JJTLESSOREQUAL: // x <= y |
| val1 = getValue(left(node)); |
| val2 = getValue(right(node)); |
| setImplicitTypes(val1, val2, null); |
| return factory.lessThanEqual(val1, val2); |
| |
| case JJTGREATERTHAN: // x > y |
| val1 = getValue(left(node)); |
| val2 = getValue(right(node)); |
| setImplicitTypes(val1, val2, null); |
| return factory.greaterThan(val1, val2); |
| |
| case JJTGREATEROREQUAL: // x >= y |
| val1 = getValue(left(node)); |
| val2 = getValue(right(node)); |
| setImplicitTypes(val1, val2, null); |
| return factory.greaterThanEqual(val1, val2); |
| |
| case JJTADD: // x + y |
| val1 = getValue(left(node)); |
| val2 = getValue(right(node)); |
| setImplicitTypes(val1, val2, TYPE_NUMBER); |
| return factory.add(val1, val2); |
| |
| case JJTSUBTRACT: // x - y |
| val1 = getValue(left(node)); |
| val2 = getValue(right(node)); |
| setImplicitTypes(val1, val2, TYPE_NUMBER); |
| return factory.subtract(val1, val2); |
| |
| case JJTMULTIPLY: // x * y |
| val1 = getValue(left(node)); |
| val2 = getValue(right(node)); |
| setImplicitTypes(val1, val2, TYPE_NUMBER); |
| return factory.multiply(val1, val2); |
| |
| case JJTDIVIDE: // x / y |
| val1 = getValue(left(node)); |
| val2 = getValue(right(node)); |
| setImplicitTypes(val1, val2, TYPE_NUMBER); |
| return factory.divide(val1, val2); |
| |
| case JJTBETWEEN: // x.field [NOT] BETWEEN 5 AND 10 |
| val1 = getValue(child(node, 0, 3)); |
| val2 = getValue(child(node, 1, 3)); |
| val3 = getValue(child(node, 2, 3)); |
| setImplicitTypes(val1, val2, null); |
| setImplicitTypes(val1, val3, null); |
| return evalNot(not, and(factory.greaterThanEqual(val1, val2), |
| factory.lessThanEqual(val1, val3))); |
| |
| case JJTIN: // x.field [NOT] IN ('a', 'b', 'c') |
| // TYPE(x...) [NOT] IN (entityTypeLiteral1,...) |
| Expression inExp = null; |
| Iterator<JPQLNode> inIterator = node.iterator(); |
| // the first child is the path |
| JPQLNode first = inIterator.next(); |
| val1 = getValue(first); |
| while (inIterator.hasNext()) { |
| JPQLNode next = inIterator.next(); |
| if (first.id == JJTTYPE && next.id == JJTTYPELITERAL) |
| val2 = getTypeLiteral(next); |
| else |
| val2 = getValue(next); |
| if (val2 instanceof Parameter) { |
| hasParameterizedInExpression = true; |
| } |
| // special case for <value> IN (<subquery>) or |
| // <value> IN (<single value>) |
| if (useContains(not, val1, val2, node)) |
| return evalNot(not, factory.contains(val2, val1)); |
| |
| // this is currently a sequence of OR expressions, since we |
| // do not have support for IN expressions |
| setImplicitTypes(val1, val2, null); |
| if (isVerticalTypeInExpr(val1, node) && not) { |
| if (inExp == null) |
| inExp = factory.notEqual(val1, val2); |
| else |
| inExp = factory.and(inExp, factory.notEqual(val1, val2)); |
| } else { |
| if (inExp == null) |
| inExp = factory.equal(val1, val2); |
| else |
| inExp = factory.or(inExp, factory.equal(val1, val2)); |
| } |
| } |
| |
| |
| // we additionally need to add in a "NOT NULL" clause, since |
| // the IN behavior that is expected by the CTS also expects |
| // to filter our NULLs |
| if (isVerticalTypeInExpr(val1, node)) |
| return inExp; |
| else |
| return and(evalNot(not, inExp), |
| factory.notEqual(val1, factory.getNull())); |
| |
| case JJTISNULL: // x.field IS [NOT] NULL |
| val1 = getValue(onlyChild(node)); |
| checkEmbeddable(val1); |
| if (not) |
| return factory.notEqual |
| (val1, factory.getNull()); |
| else |
| return factory.equal |
| (val1, factory.getNull()); |
| |
| case JJTPATH: |
| return getPathOrConstant(node); |
| |
| case JJTIDENTIFIER: |
| case JJTIDENTIFICATIONVARIABLE: |
| return getIdentifier(node); |
| |
| case JJTQUALIFIEDPATH: |
| return getQualifiedPath(node); |
| |
| case JJTQUALIFIEDIDENTIFIER: |
| // KEY(e), VALUE(e), ENTRY(e) |
| return getQualifiedIdentifier(node); |
| |
| case JJTGENERALIDENTIFIER: |
| // KEY(e), VALUE(e) |
| if (node.parent.parent.id == JJTWHERE || node.parent.id == JJTGROUPBY) |
| return getGeneralIdentifier(onlyChild(node), true); |
| return getQualifiedIdentifier(onlyChild(node)); |
| |
| case JJTNOT: |
| return factory.not(getExpression(onlyChild(node))); |
| |
| case JJTLIKE: // field LIKE '%someval%' |
| val1 = getValue(left(node)); |
| val2 = getValue(right(node)); |
| |
| setImplicitType(val1, TYPE_STRING); |
| setImplicitType(val2, TYPE_STRING); |
| |
| // look for an escape character beneath the node |
| String escape = null; |
| JPQLNode escapeNode = right(node). |
| findChildByID(JJTESCAPECHARACTER, true); |
| if (escapeNode != null) |
| escape = trimQuotes(onlyChild(escapeNode).text); |
| |
| if (not) |
| return factory.notMatches(val1, val2, "_", "%", escape); |
| else |
| return factory.matches(val1, val2, "_", "%", escape); |
| |
| case JJTISEMPTY: |
| return evalNot(not, |
| factory.isEmpty(getValue(onlyChild(node)))); |
| |
| case JJTSIZE: |
| return factory.size(getValue(onlyChild(node))); |
| |
| case JJTINDEX: |
| return factory.index(getValue(onlyChild(node))); |
| |
| case JJTUPPER: |
| val1 = getValue(onlyChild(node)); |
| setImplicitType(val1, TYPE_STRING); |
| return factory.toUpperCase(val1); |
| |
| case JJTLOWER: |
| return factory.toLowerCase(getStringValue(onlyChild(node))); |
| |
| case JJTLENGTH: |
| return factory.stringLength(getStringValue(onlyChild(node))); |
| |
| case JJTABS: |
| return factory.abs(getNumberValue(onlyChild(node))); |
| |
| case JJTSQRT: |
| return factory.sqrt(getNumberValue(onlyChild(node))); |
| |
| case JJTMOD: |
| val1 = getValue(left(node)); |
| val2 = getValue(right(node)); |
| setImplicitTypes(val1, val2, TYPE_NUMBER); |
| return factory.mod(val1, val2); |
| |
| case JJTTRIM: // TRIM([[where] [char] FROM] field) |
| val1 = getValue(lastChild(node)); |
| setImplicitType(val1, TYPE_STRING); |
| |
| Boolean trimWhere = null; |
| |
| JPQLNode firstTrimChild = firstChild(node); |
| |
| if (node.getChildCount() > 1) { |
| trimWhere = |
| firstTrimChild.id == JJTTRIMLEADING ? Boolean.TRUE |
| : |
| firstTrimChild.id == JJTTRIMTRAILING ? Boolean.FALSE |
| : null; |
| } |
| |
| Value trimChar; |
| |
| // if there are 3 children, then we know the trim |
| // char is the second node |
| if (node.getChildCount() == 3) |
| trimChar = getValue(secondChild(node)); |
| // if there are two children, then we need to check to see |
| // if the first child is a leading/trailing/both node, |
| // or the trim character node |
| else if (node.getChildCount() == 2 |
| && firstTrimChild.id != JJTTRIMLEADING |
| && firstTrimChild.id != JJTTRIMTRAILING |
| && firstTrimChild.id != JJTTRIMBOTH) |
| trimChar = getValue(firstChild(node)); |
| // othwerwise, we default to trimming the space character |
| else |
| trimChar = factory.newLiteral(" ", Literal.TYPE_STRING); |
| |
| return factory.trim(val1, trimChar, trimWhere); |
| |
| case JJTCONCAT: |
| if (node.children.length < 2) |
| throw parseException(EX_USER, "less-child-count", |
| new Object[]{2, node, |
| Arrays.asList(node.children) }, null); |
| |
| val1 = getValue(firstChild(node)); |
| val2 = getValue(secondChild(node)); |
| setImplicitType(val1, TYPE_STRING); |
| setImplicitType(val2, TYPE_STRING); |
| Value concat = factory.concat(val1, val2); |
| for (int i = 2; i < node.children.length; i++) { |
| val2 = getValue(node.children[i]); |
| setImplicitType(val2, TYPE_STRING); |
| concat = factory.concat(concat, val2); |
| } |
| return concat; |
| |
| case JJTSUBSTRING: |
| // Literals are forced to be Integers because PostgreSQL rejects Longs in SUBSTRING parameters. |
| // This however does not help if an expression like 1+1 is passed as parameter. |
| val1 = getValue(firstChild(node)); |
| JPQLNode child2 = secondChild(node); |
| if (child2.id == JJTINTEGERLITERAL) { |
| val2 = getIntegerValue(child2); |
| } else { |
| val2 = getValue(child2); |
| } |
| if (node.getChildCount() == 3) { |
| JPQLNode child3 = thirdChild(node); |
| if (child3.id == JJTINTEGERLITERAL) { |
| val3 = getIntegerValue(child3); |
| } else { |
| val3 = getValue(child3); |
| } |
| } |
| setImplicitType(val1, TYPE_STRING); |
| setImplicitType(val2, Integer.TYPE); |
| if (node.children.length == 3) |
| setImplicitType(val3, Integer.TYPE); |
| |
| return convertSubstringArguments(factory, val1, val2, val3); |
| |
| case JJTLOCATE: |
| Value locatePath = getValue(firstChild(node)); |
| Value locateSearch = getValue(secondChild(node)); |
| Value locateFromIndex = null; |
| // Literals are forced to be Integers because PostgreSQL rejects Longs in POSITION parameters. |
| // This however does not help if an expression like 1+1 is passed as parameter. |
| if (node.getChildCount() > 2) { // optional start index arg |
| JPQLNode child3 = thirdChild(node); |
| if (child3.id == JJTINTEGERLITERAL) { |
| locateFromIndex = getIntegerValue(child3); |
| } else |
| locateFromIndex = getValue(child3); |
| } |
| setImplicitType(locatePath, TYPE_STRING); |
| setImplicitType(locateSearch, TYPE_STRING); |
| |
| if (locateFromIndex != null) |
| setImplicitType(locateFromIndex, Integer.TYPE); |
| |
| return factory.indexOf(locateSearch, |
| locateFromIndex == null ? locatePath |
| : factory.newArgumentList(locatePath, locateFromIndex)); |
| |
| case JJTAGGREGATE: |
| // simply pass-through while asserting a single child |
| return eval(onlyChild(node)); |
| |
| case JJTCOUNT: |
| JPQLNode c = lastChild(node); |
| if (c.id == JJTIDENTIFIER) |
| // count(e) |
| return factory.count(getPath(node, false, true)); |
| return factory.count(getValue(c)); |
| |
| case JJTMAX: |
| return factory.max(getNumberValue(onlyChild(node))); |
| |
| case JJTMIN: |
| return factory.min(getNumberValue(onlyChild(node))); |
| |
| case JJTSUM: |
| return factory.sum(getNumberValue(onlyChild(node))); |
| |
| case JJTAVERAGE: |
| return factory.avg(getNumberValue(onlyChild(node))); |
| |
| case JJTDISTINCTPATH: |
| return factory.distinct(getValue(onlyChild(node))); |
| |
| case JJTEXISTS: |
| return factory.isNotEmpty((Value) eval(onlyChild(node))); |
| |
| case JJTANY: |
| return factory.any((Value) eval(onlyChild(node))); |
| |
| case JJTALL: |
| return factory.all((Value) eval(onlyChild(node))); |
| |
| case JJTSUBSELECT: |
| return getSubquery(node); |
| |
| case JJTMEMBEROF: |
| val1 = getValue(left(node), VAR_PATH); |
| val2 = getValue(right(node), VAR_PATH); |
| checkEmbeddable(val2); |
| setImplicitContainsTypes(val2, val1, CONTAINS_TYPE_ELEMENT); |
| return evalNot(not, factory.contains(val2, val1)); |
| |
| case JJTCURRENTDATE: |
| return factory.getCurrentDate(Date.class); |
| |
| case JJTCURRENTTIME: |
| return factory.getCurrentTime(Time.class); |
| |
| case JJTCURRENTTIMESTAMP: |
| return factory.getCurrentTimestamp(Timestamp.class); |
| |
| case JJTSELECTEXTENSION: |
| assertQueryExtensions("SELECT"); |
| return eval(onlyChild(node)); |
| |
| case JJTGROUPBYEXTENSION: |
| assertQueryExtensions("GROUP BY"); |
| return eval(onlyChild(node)); |
| |
| case JJTORDERBYEXTENSION: |
| assertQueryExtensions("ORDER BY"); |
| return eval(onlyChild(node)); |
| |
| case JJTDATELITERAL: |
| return factory.newLiteral(node.text, Literal.TYPE_DATE); |
| |
| case JJTTIMELITERAL: |
| return factory.newLiteral(node.text, Literal.TYPE_TIME); |
| |
| case JJTTIMESTAMPLITERAL: |
| return factory.newLiteral(node.text, Literal.TYPE_TIMESTAMP); |
| |
| default: |
| throw parseException(EX_FATAL, "bad-tree", |
| new Object[]{ node }, null); |
| } |
| } |
| |
| private boolean useContains(boolean not, Value val1, Value val2, JPQLNode node) { |
| if (isVerticalTypeInExpr(val1, node) && not) |
| return false; |
| else |
| return (!(val2 instanceof Literal) && node.getChildCount() == 2); |
| } |
| |
| private boolean isVerticalTypeInExpr(Value val, JPQLNode node) { |
| if (node.id != JJTIN) |
| return false; |
| return factory.isVerticalType(val); |
| } |
| |
| private Value getIntegerValue(JPQLNode node) { |
| BigDecimal bigdec = new BigDecimal |
| (node.text.endsWith("l") || node.text.endsWith("L") |
| ? node.text.substring(0, node.text.length() - 1) |
| : node.text). |
| multiply(new BigDecimal(negative(node))); |
| return factory.newLiteral(bigdec.intValue(), |
| Literal.TYPE_NUMBER); |
| } |
| |
| /** |
| * Converts JPQL substring() function to OpenJPA ExpressionFactory |
| * substring() arguments. |
| * |
| * @param val1 the original String |
| * @param val2 the 1-based start index as per JPQL substring() semantics |
| * @param val3 the length of the returned string as per JPQL semantics |
| * |
| */ |
| public static Value convertSubstringArguments(ExpressionFactory factory, |
| Value val1, Value val2, Value val3) { |
| if (val3 != null) |
| return factory.substring(val1, factory.newArgumentList(val2, val3)); |
| else |
| return factory.substring(val1, val2); |
| } |
| private void assertQueryExtensions(String clause) { |
| OpenJPAConfiguration conf = resolver.getConfiguration(); |
| switch(conf.getCompatibilityInstance().getJPQL()) { |
| case Compatibility.JPQL_WARN: |
| // check if we've already warned for this query-factory combo |
| StoreContext ctx = resolver.getQueryContext().getStoreContext(); |
| String query = currentQuery(); |
| if (ctx.getBroker() != null && query != null) { |
| String key = getClass().getName() + ":" + query; |
| BrokerFactory factory = ctx.getBroker().getBrokerFactory(); |
| Object hasWarned = factory.getUserObject(key); |
| if (hasWarned != null) |
| break; |
| else |
| factory.putUserObject(key, Boolean.TRUE); |
| } |
| Log log = conf.getLog(OpenJPAConfiguration.LOG_QUERY); |
| if (log.isWarnEnabled()) |
| log.warn(_loc.get("query-extensions-warning", clause, |
| currentQuery())); |
| break; |
| case Compatibility.JPQL_STRICT: |
| throw new ParseException(_loc.get("query-extensions-error", |
| clause, currentQuery()).getMessage()); |
| case Compatibility.JPQL_EXTENDED: |
| break; |
| default: |
| throw new IllegalStateException( |
| "Compatibility.getJPQL() == " |
| + conf.getCompatibilityInstance().getJPQL()); |
| } |
| } |
| |
| @Override |
| public void setImplicitTypes(Value val1, Value val2, |
| Class<?> expected) { |
| String currQuery = currentQuery(); |
| setImplicitTypes(val1, val2, expected, resolver, parameterTypes, |
| currQuery); |
| } |
| |
| |
| public static void setImplicitTypes(Value val1, Value val2, |
| Class<?> expected, Resolver resolver, OrderedMap<Object,Class<?>> parameterTypes, |
| String currentQuery) { |
| AbstractExpressionBuilder.setImplicitTypes(val1, val2, expected, |
| resolver); |
| |
| // as well as setting the types for conversions, we also need to |
| // ensure that any parameters are declared with the correct type, |
| // since the JPA spec expects that these will be validated |
| Parameter param = val1 instanceof Parameter ? (Parameter) val1 |
| : val2 instanceof Parameter ? (Parameter) val2 : null; |
| Path path = val1 instanceof Path ? (Path) val1 |
| : val2 instanceof Path ? (Path) val2 : null; |
| |
| // we only check for parameter-to-path comparisons |
| if (param == null || path == null || parameterTypes == null) |
| return; |
| |
| FieldMetaData fmd = path.last(); |
| if (fmd == null) |
| return; |
| |
| if (expected == null) |
| checkEmbeddable(path, currentQuery); |
| |
| Class<?> type = path.getType(); |
| if (type == null) |
| return; |
| |
| Object paramKey = param.getParameterKey(); |
| if (paramKey == null) |
| return; |
| |
| // make sure we have already declared the parameter |
| if (parameterTypes.containsKey(paramKey)) |
| parameterTypes.put(paramKey, type); |
| } |
| |
| private Value getStringValue(JPQLNode node) { |
| return getTypeValue(node, TYPE_STRING); |
| } |
| |
| private Value getNumberValue(JPQLNode node) { |
| return getTypeValue(node, TYPE_NUMBER); |
| } |
| |
| private Value getTypeValue(JPQLNode node, Class<?> implicitType) { |
| Value val = getValue(node); |
| setImplicitType(val, implicitType); |
| return val; |
| } |
| |
| private Value getSubquery(JPQLNode node) { |
| final boolean subclasses = true; |
| |
| // parse the subquery |
| ParsedJPQL parsed = new ParsedJPQL(node.parser.jpql, node); |
| Context subContext = new Context(parsed, null, ctx()); |
| contexts.push(subContext); |
| |
| ClassMetaData candidate = getCandidateMetaData(node); |
| Subquery subq = subContext.getSubquery(); |
| if (subq == null) { |
| subq = factory.newSubquery(candidate, subclasses, nextAlias()); |
| subContext.setSubquery(subq); |
| } |
| subq.setMetaData(candidate); |
| |
| // evaluate from clause for resolving variables defined in subquery |
| JPQLNode from = node.getChild(1); |
| subContext.from = evalFromClause(from, true); |
| |
| try { |
| QueryExpressions subexp = getQueryExpressions(); |
| subq.setQueryExpressions(subexp); |
| if (subexp.projections.length > 0) |
| checkEmbeddable(subexp.projections[0]); |
| return subq; |
| } finally { |
| // remove the subquery parse context |
| contexts.pop(); |
| } |
| } |
| |
| /** |
| * Creates and records the names and order of parameters. The parameters are |
| * identified by a key with its type preserved. The second argument |
| * determines whether the first argument is used as-is or converted to |
| * an Integer as parameter key. |
| * |
| * @param id the text as it appears in the parsed node |
| * @param positional if true the first argument is converted to an integer |
| * @param isCollectionValued true for collection-valued parameters |
| */ |
| private Parameter getParameter(String id, boolean positional, |
| boolean isCollectionValued) { |
| if (parameterTypes == null) |
| parameterTypes = new OrderedMap<>(); |
| Object paramKey = positional ? Integer.parseInt(id) : id; |
| if (!parameterTypes.containsKey(paramKey)) |
| parameterTypes.put(paramKey, TYPE_OBJECT); |
| |
| ClassMetaData meta = null; |
| int index; |
| if (positional) { |
| try { |
| // indexes in JPQL are 1-based, as opposed to 0-based in |
| // the core ExpressionFactory |
| index = Integer.parseInt(id) - 1; |
| } catch (NumberFormatException e) { |
| throw parseException(EX_USER, "bad-positional-parameter", |
| new Object[]{ id }, e); |
| } |
| |
| if (index < 0) |
| throw parseException(EX_USER, "bad-positional-parameter", |
| new Object[]{ id }, null); |
| } else { |
| index = parameterTypes.indexOf(id); |
| } |
| Parameter param = isCollectionValued |
| ? factory.newCollectionValuedParameter(paramKey, TYPE_OBJECT) |
| : factory.newParameter(paramKey, TYPE_OBJECT); |
| param.setMetaData(meta); |
| param.setIndex(index); |
| |
| return param; |
| } |
| |
| /** |
| * Checks to see if we should evaluate for a NOT expression. |
| */ |
| private Expression evalNot(boolean not, Expression exp) { |
| return not ? factory.not(exp) : exp; |
| } |
| |
| /** |
| * Trim off leading and trailing single-quotes, and then |
| * replace any internal '' instances with ' (since repeating the |
| * quote is the JPQL mechanism of escaping a single quote). |
| */ |
| private String trimQuotes(String str) { |
| if (str == null || str.length() <= 1) |
| return str; |
| |
| if (str.startsWith("'") && str.endsWith("'")) |
| str = str.substring(1, str.length() - 1); |
| |
| int index = -1; |
| |
| while ((index = str.indexOf("''", index + 1)) != -1) |
| str = str.substring(0, index + 1) + str.substring(index + 2); |
| |
| return str; |
| } |
| |
| /** |
| * Trim off leading and trailing double-quotes. |
| */ |
| private String trimDoubleQuotes(String str) { |
| if (str == null || str.length() <= 1) |
| return str; |
| |
| if (str.startsWith("\"") && str.endsWith("\"")) |
| str = str.substring(1, str.length() - 1); |
| |
| return str; |
| } |
| |
| /** |
| * An IntegerLiteral and DecimalLiteral node will |
| * have a child node of Negative if it is negative: |
| * if so, this method returns -1, else it returns 1. |
| */ |
| private short negative(JPQLNode node) { |
| if (node.children != null && node.children.length == 1 |
| && firstChild(node).id == JJTNEGATIVE) |
| return -1; |
| else |
| return 1; |
| } |
| |
| private Value getIdentifier(JPQLNode node) { |
| final String name = node.text; |
| final Value val = getVariable(name, false); |
| |
| ClassMetaData cmd = getMetaDataForAlias(name); |
| |
| if (cmd != null) { |
| // handle the case where the class name is the alias |
| // for the candidate (we don't use variables for this) |
| Value thiz = null; |
| if (ctx().subquery == null || |
| ctx().getSchema(name.toLowerCase()) == null) { |
| if (ctx().subquery != null && inAssignSubselectProjection) |
| thiz = factory.newPath(ctx().subquery); |
| else |
| thiz = factory.getThis(); |
| } else { |
| thiz = factory.newPath(ctx().subquery); |
| } |
| ((Path)thiz).setSchemaAlias(name); |
| thiz.setMetaData(cmd); |
| return thiz; |
| } else if (val instanceof Path) { |
| return val; |
| } else if (val instanceof Value) { |
| if (val.isVariable()) { |
| // can be an entity type literal |
| Class<?> c = resolver.classForName(name, null); |
| if (c != null) { |
| Value lit = factory.newTypeLiteral(c, Literal.TYPE_CLASS); |
| Class<?> candidate = getCandidateType(); |
| ClassMetaData can = getClassMetaData(candidate.getName(), |
| false); |
| ClassMetaData meta = getClassMetaData(name, false); |
| if (candidate.isAssignableFrom(c)) |
| lit.setMetaData(meta); |
| else |
| lit.setMetaData(can); |
| return lit; |
| } |
| } |
| return val; |
| } |
| |
| throw parseException(EX_USER, "unknown-identifier", |
| new Object[]{ name }, null); |
| } |
| |
| private Path validateMapPath(JPQLNode node, JPQLNode id) { |
| Path path = (Path) getValue(id); |
| FieldMetaData fld = path.last(); |
| |
| if (fld == null && ctx().subquery != null) { |
| Value var = getVariable(id.text, false); |
| if (var != null) { |
| path = factory.newPath(var); |
| fld = path.last(); |
| } |
| } |
| |
| if (fld != null) { |
| // validate the field is of type java.util.Map |
| if (fld.getDeclaredTypeCode() != JavaTypes.MAP) { |
| String oper = "VALUE"; |
| if (node.id == JJTENTRY) |
| oper = "ENTRY"; |
| else if (node.id == JJTKEY) |
| oper = "KEY"; |
| throw parseException(EX_USER, "bad-qualified-identifier", |
| new Object[]{ id.text, oper}, null); |
| } |
| } |
| else |
| throw parseException(EX_USER, "unknown-type", |
| new Object[]{ id.text}, null); |
| |
| return path; |
| } |
| |
| private Value getGeneralIdentifier(JPQLNode node, boolean verifyEmbeddable) { |
| JPQLNode id = onlyChild(node); |
| Path path = validateMapPath(node, id); |
| |
| if (node.id == JJTKEY) |
| path = (Path) factory.getKey(path); |
| FieldMetaData fld = path.last(); |
| ClassMetaData meta = fld.getKey().getTypeMetaData(); |
| if (verifyEmbeddable && |
| (node.id == JJTKEY && meta != null && fld.getKey().isEmbedded()) || |
| (node.id == JJTVALUE && fld.isElementCollection() && |
| fld.getElement().getEmbeddedMetaData() != null)) { |
| // check basic type |
| if (node.parent.parent.id == JJTGROUPBY) |
| throw parseException(EX_USER, "cant-groupby-key-value-embeddable", |
| new Object[]{ node.id == JJTVALUE ? "VALUE" : "KEY", id.text }, null); |
| else |
| throw parseException(EX_USER, "bad-general-identifier", |
| new Object[]{ node.id == JJTVALUE ? "VALUE" : "KEY", id.text }, null); |
| } |
| return path; |
| } |
| |
| private Value getQualifiedIdentifier(JPQLNode node) { |
| JPQLNode id = onlyChild(node); |
| Path path = validateMapPath(node, id); |
| |
| if (node.id == JJTVALUE) |
| return path; |
| |
| Value value = getValue(id); |
| if (node.id == JJTKEY) |
| return factory.mapKey(path, value); |
| else |
| return factory.mapEntry(path, value); |
| } |
| |
| private Path getQualifiedPath(JPQLNode node) { |
| return getQualifiedPath(node, false, true); |
| } |
| |
| private Path getQualifiedPath(JPQLNode node, boolean pcOnly, boolean inner) |
| { |
| int nChild = node.getChildCount(); |
| JPQLNode firstChild = firstChild(node); |
| JPQLNode id = firstChild.id == JJTKEY ? onlyChild(firstChild) : |
| firstChild; |
| Path path = validateMapPath(firstChild, id); |
| |
| if (firstChild.id == JJTIDENTIFIER) |
| return getPath(node); |
| |
| FieldMetaData fld = path.last(); |
| path = (Path) factory.getKey(path); |
| ClassMetaData meta = fld.getKey().getTypeMetaData(); |
| |
| if (meta == null) |
| throw parseException(EX_USER, "bad-qualified-path", |
| new Object[]{ id.text }, null); |
| |
| path.setMetaData(meta); |
| |
| // walk through the children and assemble the path |
| boolean allowNull = !inner; |
| for (int i = 1; i < nChild; i++) { |
| path = (Path) traversePath(path, node.children[i].text, pcOnly, |
| allowNull); |
| |
| // all traversals but the first one will always be inner joins |
| allowNull = false; |
| } |
| return path; |
| } |
| |
| private Value getTypeLiteral(JPQLNode node) { |
| JPQLNode type = onlyChild(node); |
| final String name = type.text; |
| final Value val = getVariable(name, false); |
| |
| if (val instanceof Value && val.isVariable()) { |
| Class<?> c = resolver.classForName(name, null); |
| if (c != null) { |
| Value typeLit = factory.newTypeLiteral(c, Literal.TYPE_CLASS); |
| typeLit.setMetaData(getClassMetaData(name, false)); |
| return typeLit; |
| } |
| } |
| |
| throw parseException(EX_USER, "not-type-literal", |
| new Object[]{ name }, null); |
| } |
| |
| private Value getPathOrConstant(JPQLNode node) { |
| // first check to see if the path is an enum or static field, and |
| // if so, load it |
| String className = assemble(node, ".", 1); |
| Class<?> c = resolver.classForName(className, null); |
| if (c != null) { |
| String fieldName = lastChild(node).text; |
| int type = (c.isEnum() ? Literal.TYPE_ENUM : Literal.TYPE_UNKNOWN); |
| try { |
| Field field = c.getField(fieldName); |
| Object value = field.get(null); |
| return factory.newLiteral(value, type); |
| } catch (NoSuchFieldException nsfe) { |
| if (node.inEnumPath) |
| throw parseException(EX_USER, "no-field", |
| new Object[]{ c.getName(), fieldName }, nsfe); |
| else |
| return getPath(node, false, true); |
| } catch (Exception e) { |
| throw parseException(EX_USER, "unaccessible-field", |
| new Object[]{ className, fieldName }, e); |
| } |
| } else { |
| return getPath(node, false, true); |
| } |
| } |
| |
| /** |
| * Process type_discriminator |
| * type_discriminator ::= |
| * TYPE(general_identification_variable | |
| * single_valued_object_path_expression | |
| * input_parameter ) |
| */ |
| private Value getType(JPQLNode node) { |
| switch (node.id) { |
| case JJTIDENTIFIER: |
| return factory.type(getValue(node)); |
| |
| case JJTNAMEDINPUTPARAMETER: |
| return factory.type(getParameter(node.text, false, false)); |
| |
| case JJTPOSITIONALINPUTPARAMETER: |
| return factory.type(getParameter(node.text, true, false)); |
| |
| case JJTGENERALIDENTIFIER: |
| return factory.type(getQualifiedIdentifier(onlyChild(node))); |
| |
| default: |
| // TODO: enforce jpa2.0 spec rules. |
| // A single_valued_object_field is designated by the name of |
| // an association field in a one-to-one or many-to-one relationship |
| // or a field of embeddable class type. |
| // The type of a single_valued_object_field is the abstract schema |
| // type of the related entity or embeddable class |
| Value path = getPath(node, false, true); |
| return factory.type(path); |
| } |
| } |
| |
| private Path getPath(JPQLNode node) { |
| return getPath(node, false, true); |
| } |
| |
| private Path getPath(JPQLNode node, boolean pcOnly, boolean inner) { |
| // resolve the first element against the aliases map ... |
| // i.e., the path "SELECT x.id FROM SomeClass x where x.id > 10" |
| // will need to have "x" in the alias map in order to resolve |
| Path path = null; |
| |
| final String name = firstChild(node).text; |
| final Value val = getVariable(name, false); |
| |
| // handle the case where the class name is the alias |
| // for the candidate (we don't use variables for this) |
| if (name.equalsIgnoreCase(ctx().schemaAlias)) { |
| if (ctx().subquery != null) { |
| path = factory.newPath(ctx().subquery); |
| path.setMetaData(ctx().subquery.getMetaData()); |
| } else { |
| path = factory.newPath(); |
| path.setMetaData(ctx().meta); |
| } |
| } else if (getMetaDataForAlias(name) != null) |
| path = newPath(null, getMetaDataForAlias(name)); |
| else if (val instanceof Path) |
| path = (Path) val; |
| else if (val.getMetaData() != null) |
| path = newPath(val, val.getMetaData()); |
| else |
| throw parseException(EX_USER, "path-invalid", |
| new Object[]{ assemble(node), name }, null); |
| |
| path.setSchemaAlias(name); |
| |
| // walk through the children and assemble the path |
| boolean allowNull = !inner; |
| for (int i = 1; i < node.children.length; i++) { |
| if (path.isXPath()) { |
| for (int j = i; j <node.children.length; j++) |
| path = (Path) traverseXPath(path, node.children[j].text); |
| return path; |
| } |
| path = (Path) traversePath(path, node.children[i].text, pcOnly, |
| allowNull); |
| if (ctx().getParent() != null && ctx().getVariable(path.getSchemaAlias()) == null) { |
| path.setSubqueryContext(ctx(), name); |
| } |
| |
| // all traversals but the first one will always be inner joins |
| allowNull = false; |
| } |
| |
| return path; |
| } |
| |
| @Override |
| protected Class<?> getDeclaredVariableType(String name) { |
| ClassMetaData cmd = getMetaDataForAlias(name); |
| if (cmd != null) |
| return cmd.getDescribedType(); |
| |
| if (name != null && name.equals(ctx().schemaAlias)) |
| return getCandidateType(); |
| |
| // JPQL has no declared variables |
| return null; |
| } |
| |
| /** |
| * Returns an Expression for the given node by eval'ing it. |
| */ |
| private Expression getExpression(JPQLNode node) { |
| Object exp = eval(node); |
| |
| // check for boolean values used as expressions |
| if (!(exp instanceof Expression)) |
| return factory.asExpression((Value) exp); |
| return (Expression) exp; |
| } |
| |
| /** |
| * Returns a Simple Case Expression for the given node by eval'ing it. |
| */ |
| private Value getSimpleCaseExpression(JPQLNode node) { |
| Object caseOperand = eval(node.getChild(0)); |
| int nChild = node.getChildCount(); |
| |
| Object val = eval(lastChild(node)); |
| Object exp[] = new Expression[nChild - 2]; |
| for (int i = 1; i < nChild - 1; i++) |
| exp[i-1] = eval(node.children[i]); |
| |
| return factory.simpleCaseExpression((Value) caseOperand, |
| (Expression[]) exp, (Value) val); |
| } |
| |
| /** |
| * Returns a General Case Expression for the given node by eval'ing it. |
| */ |
| private Value getGeneralCaseExpression(JPQLNode node) { |
| int nChild = node.getChildCount(); |
| |
| Object val = eval(lastChild(node)); |
| Object exp[] = new Expression[nChild - 1]; |
| for (int i = 0; i < nChild - 1; i++) |
| exp[i] = eval(node.children[i]); |
| |
| return factory.generalCaseExpression((Expression[]) exp, (Value) val); |
| } |
| |
| private Expression getWhenCondition(JPQLNode node) { |
| Object exp = eval(firstChild(node)); |
| Object val = eval(secondChild(node)); |
| return factory.whenCondition((Expression) exp, (Value) val); |
| } |
| |
| private Expression getWhenScalar(JPQLNode node) { |
| Object val1 = eval(firstChild(node)); |
| Object val2 = eval(secondChild(node)); |
| return factory.whenScalar((Value) val1, (Value) val2); |
| } |
| |
| private Value getCoalesceExpression(JPQLNode node) { |
| int nChild = node.getChildCount(); |
| |
| Object vals[] = new Value[nChild]; |
| for (int i = 0; i < nChild; i++) |
| vals[i] = eval(node.children[i]); |
| |
| return factory.coalesceExpression((Value[]) vals); |
| } |
| |
| private Value getNullIfExpression(JPQLNode node) { |
| Object val1 = eval(firstChild(node)); |
| Object val2 = eval(secondChild(node)); |
| |
| return factory.nullIfExpression((Value) val1, (Value) val2); |
| } |
| |
| private Value getValue(JPQLNode node) { |
| if (node.id == JJTQUALIFIEDIDENTIFIER) |
| return getQualifiedIdentifier(onlyChild(node)); |
| return getValue(node, VAR_PATH); |
| } |
| |
| private Path newPath(Value val, ClassMetaData meta) { |
| Path path = val == null ? factory.newPath() : factory.newPath(val); |
| if (meta != null) |
| path.setMetaData(meta); |
| return path; |
| } |
| |
| /** |
| * Returns a Value for the given node by eval'ing it. |
| */ |
| private Value getValue(JPQLNode node, int handleVar) { |
| Value val = (Value) eval(node); |
| |
| // determined how to evaluate a variable |
| if (!val.isVariable()) |
| return val; |
| else if (handleVar == VAR_PATH && !(val instanceof Path)) |
| return newPath(val, val.getMetaData()); |
| else if (handleVar == VAR_ERROR) |
| throw parseException(EX_USER, "unexpected-var", |
| new Object[]{ node.text }, null); |
| else |
| return val; |
| } |
| |
| //////////////////////////// |
| // Parse Context Management |
| //////////////////////////// |
| |
| private Context ctx() { |
| return contexts.peek(); |
| } |
| |
| private JPQLNode root() { |
| return ctx().parsed.root; |
| } |
| |
| private ClassMetaData getMetaDataForAlias(String alias) { |
| for (int i = contexts.size() - 1; i >= 0; i--) { |
| Context context = contexts.get(i); |
| if (alias.equalsIgnoreCase(context.schemaAlias)) |
| return context.meta; |
| } |
| |
| return null; |
| } |
| |
| @Override |
| protected void addSchemaToContext(String id, ClassMetaData meta) { |
| ctx().addSchema(id.toLowerCase(), meta); |
| } |
| |
| @Override |
| protected void addVariableToContext(String id, Value var) { |
| ctx().addVariable(id, var); |
| } |
| |
| @Override |
| protected Value getVariable(String var) { |
| Context c = ctx(); |
| Value v = c.getVariable(var); |
| if (v != null) |
| return v; |
| if (c.getParent() != null) |
| return c.getParent().findVariable(var); |
| |
| return null; |
| } |
| |
| //////////////////////////// |
| // Node traversal utilities |
| //////////////////////////// |
| |
| private JPQLNode onlyChild(JPQLNode node) |
| throws UserException { |
| JPQLNode child = firstChild(node); |
| |
| if (node.children.length > 1) |
| throw parseException(EX_USER, "multi-children", |
| new Object[]{ node, Arrays.asList(node.children) }, null); |
| |
| return child; |
| } |
| |
| /** |
| * Returns the left node (the first of the children), and asserts |
| * that there are exactly two children. |
| */ |
| private JPQLNode left(JPQLNode node) { |
| return child(node, 0, 2); |
| } |
| |
| /** |
| * Returns the right node (the second of the children), and asserts |
| * that there are exactly two children. |
| */ |
| private JPQLNode right(JPQLNode node) { |
| return child(node, 1, 2); |
| } |
| |
| private JPQLNode child(JPQLNode node, int childNum, int assertCount) { |
| if (node.children.length != assertCount) |
| throw parseException(EX_USER, "wrong-child-count", |
| new Object[]{assertCount, node, |
| Arrays.asList(node.children) }, null); |
| |
| return node.children[childNum]; |
| } |
| |
| private JPQLNode firstChild(JPQLNode node) { |
| if (node.children == null || node.children.length == 0) |
| throw parseException(EX_USER, "no-children", |
| new Object[]{ node }, null); |
| return node.children[0]; |
| } |
| |
| private static JPQLNode secondChild(JPQLNode node) { |
| return node.children[1]; |
| } |
| |
| private static JPQLNode thirdChild(JPQLNode node) { |
| return node.children[2]; |
| } |
| |
| private static JPQLNode lastChild(JPQLNode node) { |
| return lastChild(node, 0); |
| } |
| |
| /** |
| * The Nth from the last child. E.g., |
| * lastChild(1) will return the second-to-the-last child. |
| */ |
| private static JPQLNode lastChild(JPQLNode node, int fromLast) { |
| return node.children[node.children.length - (1 + fromLast)]; |
| } |
| |
| /** |
| * Base node that will be generated by the JPQLExpressionBuilder; base |
| * class of the {@link SimpleNode} that is used by {@link JPQL}. |
| * |
| * @author Marc Prud'hommeaux |
| * @see Node |
| * @see SimpleNode |
| */ |
| protected abstract static class JPQLNode |
| implements Node, Serializable { |
| |
| private static final long serialVersionUID = 1L; |
| final int id; |
| final JPQL parser; |
| JPQLNode parent; |
| JPQLNode[] children; |
| String text; |
| boolean not = false; |
| boolean inEnumPath = false; |
| |
| public JPQLNode(JPQL parser, int id) { |
| this.id = id; |
| this.parser = parser; |
| this.inEnumPath = parser.inEnumPath; |
| } |
| |
| public void jjtOpen() { |
| } |
| |
| public void jjtClose() { |
| } |
| |
| JPQLNode[] findChildrenByID(int id) { |
| Collection<JPQLNode> set = new LinkedHashSet<>(); |
| findChildrenByID(id, set); |
| return set.toArray(new JPQLNode[set.size()]); |
| } |
| |
| private void findChildrenByID(int id, Collection<JPQLNode> set) { |
| for (int i = 0; children != null && i < children.length; i++) { |
| if (children[i].id == id) |
| set.add(children[i]); |
| |
| children[i].findChildrenByID(id, set); |
| } |
| } |
| |
| boolean hasChildID(int id) { |
| return findChildByID(id, false) != null; |
| } |
| |
| JPQLNode findChildByID(int id, boolean recurse) { |
| for (int i = 0; children != null && i < children.length; i++) { |
| JPQLNode child = children[i]; |
| |
| if (child.id == id) |
| return children[i]; |
| |
| if (recurse) { |
| JPQLNode found = child.findChildByID(id, recurse); |
| if (found != null) |
| return found; |
| } |
| } |
| |
| // not found |
| return null; |
| } |
| |
| public void jjtSetParent(Node parent) { |
| this.parent = (JPQLNode) parent; |
| } |
| |
| public Node jjtGetParent() { |
| return this.parent; |
| } |
| |
| public void jjtAddChild(Node n, int i) { |
| if (children == null) { |
| children = new JPQLNode[i + 1]; |
| } else if (i >= children.length) { |
| JPQLNode c[] = new JPQLNode[i + 1]; |
| System.arraycopy(children, 0, c, 0, children.length); |
| children = c; |
| } |
| |
| children[i] = (JPQLNode) n; |
| } |
| |
| public Node jjtGetChild(int i) { |
| return children[i]; |
| } |
| |
| public int getChildCount() { |
| return jjtGetNumChildren(); |
| } |
| |
| public JPQLNode getChild(int index) { |
| return (JPQLNode) jjtGetChild(index); |
| } |
| |
| public Iterator<JPQLNode> iterator() { |
| return Arrays.asList(children).iterator(); |
| } |
| |
| public int jjtGetNumChildren() { |
| return (children == null) ? 0 : children.length; |
| } |
| |
| void setText(String text) { |
| this.text = text; |
| } |
| |
| void setToken(Token t) { |
| setText(t.image); |
| } |
| |
| @Override |
| public String toString() { |
| return JPQLTreeConstants.jjtNodeName[this.id]; |
| } |
| |
| public String toString(String prefix) { |
| return prefix + toString(); |
| } |
| |
| /** |
| * Debugging method. |
| * |
| * @see #dump(java.io.PrintStream,String) |
| */ |
| public void dump(String prefix) { |
| dump(System.out, prefix); |
| } |
| |
| public void dump() { |
| dump(" "); |
| } |
| |
| /** |
| * Debugging method to output a parse tree. |
| * |
| * @param out the stream to which to write the debugging info |
| * @param prefix the prefix to write out before lines |
| */ |
| public void dump(PrintStream out, String prefix) { |
| dump(out, prefix, false); |
| } |
| |
| public void dump(PrintStream out, String prefix, boolean text) { |
| out.println(toString(prefix) |
| + (text && this.text != null ? " [" + this.text + "]" : "")); |
| if (children != null) { |
| for (int i = 0; i < children.length; ++i) { |
| JPQLNode n = children[i]; |
| if (n != null) { |
| n.dump(out, prefix + " ", text); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Public for unit testing purposes. |
| */ |
| public static class ParsedJPQL implements Serializable { |
| private static final long serialVersionUID = 1L; |
| |
| // This is only ever used during parse; when ParsedJPQL instances |
| // are serialized, they will have already been parsed. |
| private final transient JPQLNode root; |
| |
| private final String query; |
| |
| // cache of candidate type data. This is stored here in case this |
| // parse tree is reused in a context that does not know what the |
| // candidate type is already. |
| private Class<?> _candidateType; |
| |
| ParsedJPQL(String jpql) { |
| this(jpql, parse(jpql)); |
| } |
| |
| ParsedJPQL(String query, JPQLNode root) { |
| this.root = root; |
| this.query = query; |
| } |
| |
| private static JPQLNode parse(String jpql) { |
| if (jpql == null) |
| jpql = ""; |
| |
| try { |
| return (JPQLNode) new JPQL(jpql).parseQuery(); |
| } catch (Error e) { |
| // special handling for Error subclasses, which the |
| // parser may sometimes (unfortunately) throw |
| throw new UserException(_loc.get("parse-error", |
| new Object[]{ e.toString(), jpql })); |
| } |
| } |
| |
| void populate(ExpressionStoreQuery query) { |
| QueryContext ctx = query.getContext(); |
| |
| // if the owning query's context does not have |
| // any candidate class, then set it here |
| if (ctx.getCandidateType() == null) { |
| if (_candidateType == null) |
| _candidateType = new JPQLExpressionBuilder |
| (null, query, this).getCandidateType(); |
| ctx.setCandidateType(_candidateType, true); |
| } |
| } |
| |
| /** |
| * Public for unit testing purposes. |
| */ |
| public Class<?> getCandidateType() { |
| return _candidateType; |
| } |
| |
| @Override |
| public String toString () |
| { |
| return this.query; |
| } |
| } |
| |
| |
| // throws an exception if there are numeric parameters which do not start with 1. |
| private void validateParameters() { |
| if (parameterTypes == null || parameterTypes.isEmpty()) { |
| return; |
| } |
| |
| boolean numericParms = false; |
| boolean namedParms = false; |
| |
| for (Object key : parameterTypes.keySet()) { |
| |
| if (key instanceof Number) { |
| if (namedParms) { |
| throw new UserException(_loc.get("mixed-parameter-types", resolver.getQueryContext() |
| .getQueryString(), parameterTypes.keySet().toString())); |
| } |
| numericParms = true; |
| } else { |
| if (numericParms) { |
| throw new UserException(_loc.get("mixed-parameter-types", resolver.getQueryContext() |
| .getQueryString(), parameterTypes.keySet().toString())); |
| } |
| namedParms = true; |
| } |
| } |
| |
| if (numericParms) { |
| if (!parameterTypes.keySet().contains(1)) { |
| throw new UserException(_loc.get("missing-positional-parameter", resolver.getQueryContext() |
| .getQueryString(), parameterTypes.keySet().toString())); |
| } |
| } |
| } |
| } |
| |