/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.amber.query; import com.caucho.amber.AmberException; import com.caucho.amber.entity.AmberEntityHome; import com.caucho.amber.expr.*; import com.caucho.amber.expr.fun.*; import com.caucho.amber.manager.AmberPersistenceUnit; import com.caucho.amber.table.ForeignColumn; import com.caucho.amber.table.LinkColumns; import com.caucho.amber.table.AmberTable; import com.caucho.amber.type.EntityType; import com.caucho.amber.type.AmberType; import com.caucho.jdbc.JdbcMetaData; import com.caucho.util.CharBuffer; import com.caucho.util.IntMap; import com.caucho.util.L10N; import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.logging.Logger; /** * Contains the parser for EJB 3.0 style queries and stores * the parsed expressions. */ public class QueryParser { static final Logger log = Logger.getLogger(QueryParser.class.getName()); static final L10N L = new L10N(QueryParser.class); public final static int IDENTIFIER = 128; public final static int INTEGER = IDENTIFIER + 1; public final static int LONG = INTEGER + 1; public final static int DOUBLE = LONG + 1; public final static int STRING = DOUBLE + 1; public final static int TRUE = STRING + 1; public final static int FALSE = TRUE + 1; public final static int UNKNOWN = FALSE + 1; public final static int MEMBER = UNKNOWN + 1; public final static int OF = MEMBER + 1; public final static int EMPTY = OF + 1; public final static int NULL = EMPTY + 1; public final static int FROM = NULL + 1; public final static int IN = FROM + 1; public final static int SELECT = IN + 1; public final static int UPDATE = SELECT + 1; public final static int DELETE = UPDATE + 1; public final static int DISTINCT = DELETE + 1; public final static int WHERE = DISTINCT + 1; public final static int AS = WHERE + 1; public final static int SET = AS + 1; public final static int ORDER = SET + 1; public final static int GROUP = ORDER + 1; public final static int BY = GROUP + 1; public final static int ASC = BY + 1; public final static int DESC = ASC + 1; public final static int LIMIT = DESC + 1; public final static int OFFSET = LIMIT + 1; public final static int JOIN = OFFSET + 1; public final static int INNER = JOIN + 1; public final static int LEFT = INNER + 1; public final static int OUTER = LEFT + 1; public final static int FETCH = OUTER + 1; public final static int BETWEEN = FETCH + 1; public final static int LIKE = BETWEEN + 1; public final static int ESCAPE = LIKE + 1; public final static int IS = ESCAPE + 1; public final static int CONCAT_OP = IS + 1; public final static int EQ = CONCAT_OP + 1; public final static int NE = EQ + 1; public final static int LT = NE + 1; public final static int LE = LT + 1; public final static int GT = LE + 1; public final static int GE = GT + 1; public final static int AND = GE + 1; public final static int OR = AND + 1; public final static int NOT = OR + 1; public final static int LENGTH = NOT + 1; public final static int LOCATE = LENGTH + 1; public final static int ABS = LOCATE + 1; public final static int SQRT = ABS + 1; public final static int MOD = SQRT + 1; public final static int SIZE = MOD + 1; public final static int MAX = SIZE + 1; public final static int MIN = MAX + 1; public final static int SUM = MIN + 1; public final static int CONCAT = SUM + 1; public final static int LOWER = CONCAT + 1; public final static int UPPER = LOWER + 1; public final static int SUBSTRING = UPPER + 1; public final static int TRIM = SUBSTRING + 1; public final static int BOTH = TRIM + 1; public final static int LEADING = BOTH + 1; public final static int TRAILING = LEADING + 1; public final static int CURRENT_DATE = TRAILING + 1; public final static int CURRENT_TIME = CURRENT_DATE + 1; public final static int CURRENT_TIMESTAMP = CURRENT_TIME + 1; public final static int EXTERNAL_DOT = CURRENT_TIMESTAMP + 1; public final static int ARG = EXTERNAL_DOT + 1; public final static int NAMED_ARG = ARG + 1; public final static int NEW = NAMED_ARG + 1; public final static int THIS = NEW + 1; public final static int NOT_NULL = THIS + 1; public final static int HAVING = NOT_NULL + 1; private static IntMap _reserved; private AmberPersistenceUnit _persistenceUnit; // The query private String _sql; /* // list of the relation links private ArrayList<LinkItem> _linkList; // select expression private Expr _selectExpr; // is distinct (set) private boolean _isDistinct; */ // True if entities should be lazily loaded private boolean _isLazyResult; // The select query private AbstractQuery _query; // list of relations private HashMap<PathExpr,PathExpr> _pathMap = new HashMap<PathExpr,PathExpr>(); // parse index private int _parseIndex; // current token private int _token; // unique private int _unique; // parameter count private int _parameterCount; // temp for parsing private String _lexeme; private ArrayList<ArgExpr> _argList = new ArrayList<ArgExpr>(); private HashMap<AmberExpr, String> _joinFetchMap; ArrayList<AmberExpr> _groupList = null; private int _sqlArgCount; private FromItem.JoinSemantics _joinSemantics = FromItem.JoinSemantics.UNKNOWN; private boolean _isJoinFetch = false; // Parsing control variable, jpa/0tp4 (TRIM FROM) // SELECT .._depth=0.. TRIM(.._depth=1.. 'a' FROM o.d1) .._depth=0 FROM ... private int _depth = 0; private boolean _parsingResult; private boolean _parsingFrom; private boolean _parsingHaving; // jpa/119l: WHERE SIZE(xxx) > 0 => GROUP BY ... HAVING COUNT(xxx) > 0 private boolean _isSizeFunExpr; private AmberExpr _havingExpr; // jpa/1199 ArrayList<AmberExpr> _appendResultList = null; private boolean _isDerbyDBMS; private boolean _isPostgresDBMS; /** * Creates the query parser. */ public QueryParser(String query) { _sql = query; } /** * Returns true for Derby-like DBMS. */ public boolean isDerbyDBMS() { return _isDerbyDBMS; } /** * Returns true for Postgres-like DBMS. */ public boolean isPostgresDBMS() { return _isPostgresDBMS; } /** * Sets the persistence unit. */ public void setPersistenceUnit(AmberPersistenceUnit persistenceUnit) { _persistenceUnit = persistenceUnit; _isDerbyDBMS = false; _isPostgresDBMS = false; if (persistenceUnit == null) return; _isDerbyDBMS = ! persistenceUnit.hasPositionFunction(); _isPostgresDBMS = persistenceUnit.getFalseLiteral().equalsIgnoreCase("false"); } /** * Sets true for lazy loading. */ public void setLazyResult(boolean isLazy) { _isLazyResult = isLazy; } /** * Returns the query string */ public String getQuery() { return _sql; } /** * Returns the query string */ public AbstractQuery getSelectQuery() { return _query; } /** * Initialize the parse. */ private void init() { _parseIndex = 0; _unique = 0; _token = -1; _depth = 0; _parsingResult = false; _parsingFrom = false; _parsingHaving = false; _havingExpr = null; _appendResultList = null; _groupList = null; _joinFetchMap = new HashMap<AmberExpr, String>(); } /** * Generates a new arg. */ public int generateSQLArg() { return _sqlArgCount++; } /** * Parses the query. */ public AbstractQuery parse() throws AmberException { /* _query = query; _fromList = new ArrayList<FromItem>(); _pathMap = new HashMap<Expr,Expr>(); _idMap = new HashMap<String,PathExpr>(); _argList = new ArrayList<Expr>(); _linkList = new ArrayList<LinkItem>(); */ init(); int token = scanToken(); if (token == UPDATE) return parseUpdate(); else if (token == DELETE) return parseDelete(); _token = token; return parseSelect(false); } private AmberSelectQuery parseSelect(boolean innerSelect) throws QueryParseException { int oldParseIndex = _parseIndex; int oldToken = _token; FromItem.JoinSemantics oldJoinSemantics = _joinSemantics; boolean oldIsJoinFetch = _isJoinFetch; AbstractQuery oldQuery = _query; int oldDepth = _depth; AmberExpr oldHavingExpr = _havingExpr; ArrayList oldAppendResultList = _appendResultList; // Reset depth: subselect _depth = 0; _havingExpr = null; _appendResultList = null; AmberSelectQuery query = new AmberSelectQuery(_sql, getMetaData()); query.setParentQuery(_query); _query = query; int token; while ((token = scanToken()) >= 0 && ((token != FROM) || (_depth > 0))) { } // "SELECT CURRENT_DATE" does NOT have a FROM clause. boolean hasFrom = (token == FROM); _token = token; if (hasFrom) { _parsingFrom = true; do { scanToken(); _isJoinFetch = false; if (token == JOIN) { if ((token = peekToken()) == FETCH) { scanToken(); _isJoinFetch = true; } } FromItem from = parseFrom(); token = peekToken(); _joinSemantics = FromItem.JoinSemantics.UNKNOWN; if (token == INNER) { scanToken(); token = peekToken(); if (token != JOIN) { throw error(L.l("expected JOIN at {0}", tokenName(token))); } _joinSemantics = FromItem.JoinSemantics.INNER; } else if (token == LEFT) { scanToken(); token = peekToken(); if (token == OUTER) { scanToken(); token = peekToken(); } if (token != JOIN) throw error(L.l("expected JOIN at {0}", tokenName(token))); _joinSemantics = FromItem.JoinSemantics.OUTER; } else if (token == JOIN) { _joinSemantics = FromItem.JoinSemantics.INNER; } } while ((token == ',') || (token == JOIN)); _parsingFrom = false; } int fromParseIndex = _parseIndex; int fromToken = _token; _parseIndex = oldParseIndex; _token = oldToken; ArrayList<AmberExpr> resultList = new ArrayList<AmberExpr>(); _parsingResult = true; if (peekToken() == SELECT) { scanToken(); if (peekToken() == DISTINCT) { scanToken(); query.setDistinct(true); } String constructorName = null; if (peekToken() == NEW) { scanToken(); // Scans the fully qualified constructor String s = ""; boolean isDot = false; while ((token = scanToken()) != '(') { if (! isDot) { s += _lexeme; isDot = true; } else if (token == '.') { s += '.'; isDot = false; } else throw error(L.l("Constructor with SELECT NEW must be fully qualified. Expected '.' at {0}", tokenName(token))); } constructorName = s; } do { AmberExpr expr = parseExpr(); if (! hasFrom) { if (! (expr instanceof DateTimeFunExpr)) throw error(L.l("expected FROM clause when the select clause has not date/time functions only")); } else { // jpa/1199 if (expr == null) continue; expr = expr.bindSelect(this); if (_isLazyResult) { } else if (expr instanceof PathExpr) { PathExpr pathExpr = (PathExpr) expr; FromItem rootItem = null; AmberType targetType = pathExpr.getTargetType(); // jpa/0w24 if (targetType instanceof EntityType) { EntityType relatedType = (EntityType) targetType; EntityType parentType = relatedType; while (parentType.getParentType() != null) { if (parentType.getParentType() instanceof EntityType) parentType = parentType.getParentType(); else break; } // jpa/0l4b if (parentType != relatedType) { FromItem child = pathExpr.getChildFromItem(); AmberTable table = relatedType.getTable(); // parentType.getTable(); ArrayList<LinkColumns> outgoingLinks = table.getOutgoingLinks(); for (LinkColumns link : outgoingLinks) { if (link.getTargetTable().equals(parentType.getTable())) { rootItem = addFromItem((EntityType) parentType, parentType.getTable()); JoinExpr join = new ManyToOneJoinExpr(link, rootItem, child); rootItem.setJoinExpr(join); rootItem.setJoinSemantics(FromItem.JoinSemantics.INNER); break; } } } } expr = LoadExpr.create(pathExpr, rootItem); expr = expr.bindSelect(this); } } resultList.add(expr); } while ((token = scanToken()) == ','); query.setHasFrom(hasFrom); if (hasFrom && (constructorName != null)) { if (token != ')') throw error(L.l("Expected ')' at {0} when calling constructor with SELECT NEW", tokenName(token))); token = scanToken(); try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class cl = Class.forName(constructorName, false, loader); query.setConstructorClass(cl); } catch (ClassNotFoundException ex) { throw error(L.l("Unable to find class {0}. Make sure the class is fully qualified.", constructorName)); } } _token = token; } if (hasFrom && (peekToken() != FROM)) throw error(L.l("expected FROM at {0}", tokenName(token))); if (resultList.size() == 0) { if (_joinFetchMap.size() > 0) throw error(L.l("All associations referenced by JOIN FETCH must belong to an entity that is returned as a result of the query")); ArrayList<FromItem> fromList = _query.getFromList(); if (fromList.size() > 0) { FromItem fromItem = fromList.get(0); AmberExpr expr = fromItem.getIdExpr(); if (_isLazyResult) { } else if (expr instanceof PathExpr) { PathExpr pathExpr = (PathExpr) expr; expr = LoadExpr.create(pathExpr); expr = expr.bindSelect(this); } resultList.add(expr); } } else if (hasFrom) { int size = resultList.size(); int matches = 0; for (int i = 0; i < size; i++) { AmberExpr expr = resultList.get(i); if (expr instanceof LoadEntityExpr) { expr = ((LoadEntityExpr) expr).getExpr(); if (_joinFetchMap.get(expr) != null) { matches++; } } } if (matches < _joinFetchMap.size()) throw error(L.l("All associations referenced by JOIN FETCH must belong to an entity that is returned as a result of the query")); } // jpa/1199 if (_appendResultList != null) resultList.addAll(_appendResultList); query.setResultList(resultList); _parsingResult = false; _parseIndex = fromParseIndex; _token = fromToken; token = peekToken(); boolean hasWhere = false; if (token == WHERE) { scanToken(); hasWhere = true; AmberExpr expr = parseExpr(); // jpa/119l: WHERE SIZE() is moved to HAVING COUNT() if (expr != null) { expr = expr.createBoolean(); query.setWhere(expr.bindSelect(this)); } } boolean hasGroupBy = false; ArrayList<AmberExpr> groupList = _groupList; token = peekToken(); if (token == GROUP) { scanToken(); if (peekToken() == BY) { scanToken(); hasGroupBy = true; } if (groupList == null) groupList = new ArrayList<AmberExpr>(); while (true) { // jpa/0w23 AmberExpr groupExpr = parseExpr(); groupExpr = groupExpr.bindSelect(this); if (groupExpr instanceof PathExpr) { // jpa/119n PathExpr pathExpr = (PathExpr) groupExpr; groupExpr = LoadExpr.create(pathExpr); groupExpr = groupExpr.bindSelect(this); } groupList.add(groupExpr); if (peekToken() == ',') scanToken(); else break; } query.setGroupList(groupList); // Reset temp group list after parsing subselect. _groupList = null; } token = peekToken(); if (token == HAVING) { if (! hasGroupBy) throw error(L.l("Use of HAVING without GROUP BY is not currently supported")); _parsingHaving = true; scanToken(); AmberExpr havingExpr = parseExpr(); // jpa/119l: SIZE() if (_havingExpr != null) havingExpr = AndExpr.create(havingExpr, _havingExpr); query.setHaving(havingExpr.createBoolean().bindSelect(this)); _parsingHaving = false; } else if (hasWhere && _havingExpr != null) { // jpa/1199, jpa/119l query.setHaving(_havingExpr.createBoolean().bindSelect(this)); } token = peekToken(); if (token == ORDER) { scanToken(); if (peekToken() == BY) scanToken(); ArrayList<AmberExpr> orderList = new ArrayList<AmberExpr>(); ArrayList<Boolean> ascList = new ArrayList<Boolean>(); while (true) { AmberExpr expr = parseExpr(); // jpa/1114 if (isCollectionExpr(expr)) throw error(L.l("Unexpected collection at ORDER BY '{0}'.", expr.getClass().getName())); expr = expr.bindSelect(this); orderList.add(expr); if (peekToken() == DESC) { scanToken(); ascList.add(Boolean.FALSE); } else if (peekToken() == ASC) { scanToken(); ascList.add(Boolean.TRUE); } else ascList.add(Boolean.TRUE); if (peekToken() == ',') scanToken(); else break; } query.setOrderList(orderList, ascList); } token = peekToken(); if (token == OFFSET) { scanToken(); token = scanToken(); if (token != INTEGER) throw error(L.l("Expected INTEGER at {0}", tokenName(token))); int offset = Integer.parseInt(_lexeme); token = peekToken(); query.setOffset(offset); } if (token == LIMIT) { scanToken(); token = scanToken(); if (token != INTEGER) throw error(L.l("Expected INTEGER at {0}", tokenName(token))); int limit = Integer.parseInt(_lexeme); query.setLimit(limit); token = peekToken(); } if (! innerSelect) { query.setJoinFetchMap(_joinFetchMap); if (token > 0) throw error(L.l("expected end of query at {0}", tokenName(token))); if (! query.setArgList(_argList.toArray(new ArgExpr[_argList.size()]))) throw error(L.l("Unable to parse all query parameters. Make sure named parameters are not mixed with positional parameters")); } query.init(); _joinSemantics = oldJoinSemantics; _isJoinFetch = oldIsJoinFetch; _query = oldQuery; _depth = oldDepth; _havingExpr = oldHavingExpr; _appendResultList = oldAppendResultList; return query; } private AbstractQuery parseUpdate() throws QueryParseException { UpdateQuery query = new UpdateQuery(_sql, getMetaData()); _query = query; FromItem fromItem = parseFrom(); int token = scanToken(); if (token != SET) throw error(L.l("expected 'SET' at {0}", tokenName(token))); ArrayList<AmberExpr> fields = new ArrayList<AmberExpr>(); ArrayList<AmberExpr> values = new ArrayList<AmberExpr>(); parseSetValues(fromItem, fields, values); query.setFieldList(fields); query.setValueList(values); token = scanToken(); if (token == WHERE) { AmberExpr expr = parseExpr(); query.setWhere(expr.createBoolean().bindSelect(this)); token = scanToken(); } if (token >= 0) throw error(L.l("'{0}' not expected at end of query.", tokenName(token))); if (! query.setArgList(_argList.toArray(new ArgExpr[_argList.size()]))) throw error(L.l("Unable to parse all query parameters. Make sure named parameters are not mixed with positional parameters")); query.init(); return query; } private AbstractQuery parseDelete() throws QueryParseException { DeleteQuery query = new DeleteQuery(_sql, getMetaData()); _query = query; int token = peekToken(); if (token == FROM) scanToken(); FromItem fromItem = parseFrom(); token = scanToken(); if (token == WHERE) { query.setWhere(parseExpr().createBoolean().bindSelect(this)); token = scanToken(); } if (token >= 0) throw error(L.l("'{0}' not expected at end of query.", tokenName(token))); if (! query.setArgList(_argList.toArray(new ArgExpr[_argList.size()]))) throw error(L.l("Unable to parse all query parameters. Make sure named parameters are not mixed with positional parameters")); query.init(); return query; } /** * Parses the set values. */ private void parseSetValues(FromItem fromItem, ArrayList<AmberExpr> fields, ArrayList<AmberExpr> values) throws QueryParseException { EntityType entity = fromItem.getEntityType(); int token = -1; do { token = scanToken(); AmberExpr expr = null; String name = _lexeme.toString(); IdExpr tableExpr = getIdentifier(name); if (tableExpr != null) { expr = parsePath(tableExpr); } else { tableExpr = fromItem.getIdExpr(); AmberExpr next = tableExpr.createField(this, name); if (next instanceof PathExpr) expr = addPath((PathExpr) next); else if (next != null) expr = next; } expr = expr.bindSelect(this); fields.add(expr); if ((token = peekToken()) != EQ) throw error(L.l("expected '=' at {0}", tokenName(token))); scanToken(); // jpa/1222 expr = parseSimpleTerm(); expr = parseConcatExpr(); if (expr.hasRelationship()) throw error(L.l("UPDATE cannot set values with relationships. Unexpected path expression at {0}", expr)); expr = expr.bindSelect(this); values.add(expr); } while ((token = scanToken()) == ','); _token = token; } /** * Parses the FROM block. parseFrom's effect is to populate the * core identifiers. * * <pre> * from-item ::= schema AS? IDENTIFIER * </pre> */ private FromItem parseFrom() throws QueryParseException { SchemaExpr schema = parseSchema(); String id; int token = peekToken(); if (token == AS) { scanToken(); token = peekToken(); id = parseIdentifier(); } else if (token == IDENTIFIER) id = parseIdentifier(); else { // jpa/116c if (schema instanceof OneToManySchemaExpr) id = createTableName(); else id = schema.getTailName(); } /* AmberEntityHome home = _persistenceUnit.getHomeBySchema(schema); if (home == null) throw error(L.l("`{0}' is an unknown persistent class.", schema)); */ FromItem item = schema.addFromItem(this, id); if (schema instanceof EmbeddedSchemaExpr) { // jpa/0w22 EmbeddedSchemaExpr embeddedSchema = (EmbeddedSchemaExpr) schema; _query.addEmbeddedAlias(id, embeddedSchema.getExpr()); // pathString); } // jpa/114h item.setJoinSemantics(_joinSemantics); return item; } /** * Adds a new FromItem. */ public FromItem addFromItem(AmberTable table) { return addFromItem(null, table, createTableName()); } /** * Adds a new FromItem. */ public FromItem addFromItem(EntityType entityType, AmberTable table) { return addFromItem(entityType, table, createTableName()); } /** * Returns a unique table name */ public String createTableName() { return "caucho" + _unique++; } /** * Adds a new FromItem. */ public FromItem addFromItem(AmberTable table, String id) { return addFromItem(null, table, id); } /** * Adds a new FromItem. */ public FromItem addFromItem(EntityType entityType, AmberTable table, String id) { if (id == null) id = createTableName(); FromItem item = _query.createFromItem(entityType, table, id); item.setJoinSemantics(_joinSemantics); return item; } /** * Adds a new FromItem. */ public FromItem createDependentFromItem(FromItem item, LinkColumns link) { item = _query.createDependentFromItem(item, link, createTableName()); item.setJoinSemantics(_joinSemantics); return item; } /** * Adds a new link */ void addLink(AmberExpr expr) { // _andExpr.add(expr); throw new IllegalStateException(); } /** * Adds an entity path */ public PathExpr addPath(PathExpr path) { PathExpr oldPath = _pathMap.get(path); if (oldPath != null) return oldPath; _pathMap.put(path, path); return path; } /** * Adds a new argument */ public void addArg(ArgExpr arg) { _argList.add(arg); } /** * Parses a schema. */ private SchemaExpr parseSchema() throws QueryParseException { int token = peekToken(); boolean isIn = token == IN; if (isIn) { scanToken(); _joinSemantics = FromItem.JoinSemantics.INNER; if ((token = scanToken()) != '(') throw error(L.l("expected '(' at '{0}'", tokenName(token))); } String name = parseIdentifier(); SchemaExpr schema = null; if (! isIn) { AmberEntityHome home = _persistenceUnit.getHomeBySchema(name); if (home != null) { EntityType type = home.getEntityType(); schema = new TableIdExpr(home.getEntityType(), type.getTable().getName()); } } IdExpr id = null; if (schema == null) { id = getIdentifier(name); if (id != null) schema = new FromIdSchemaExpr(id); } if (! isIn && schema == null) { while (peekToken() == '.') { scanToken(); String segment = parseIdentifier(); name = name + '.' + segment; AmberEntityHome home = _persistenceUnit.getHomeBySchema(name); if (home != null) { schema = new TableIdExpr(home.getEntityType(), name); break; } } } if (schema == null) { throw error(L.l("'{0}' is an unknown entity.", name)); } name = ""; boolean isFirst = true; while (peekToken() == '.') { scanToken(); String segment = parseIdentifier(); if (isFirst) { name += segment; isFirst = false; } else name += "." + segment; schema = schema.createField(this, segment); } if (_isJoinFetch && (! name.equals(""))) { _joinFetchMap.put(id, name); } if (isIn) { if ((token = scanToken()) != ')') throw error(L.l("expected ')' at '{0}'", tokenName(token))); } return schema; } /** * Parses an expression. */ private AmberExpr parseExpr() throws QueryParseException { if (peekToken() == SELECT) { AmberSelectQuery select = parseSelect(true); return new SubSelectExpr(select); } AmberExpr expr = parseOrExpr(); return expr; // .bindSelect(this); } /** * Parses an or expression. */ private AmberExpr parseOrExpr() throws QueryParseException { AmberExpr expr = parseAndExpr(); OrExpr orExpr = null; while (peekToken() == OR) { scanToken(); if (orExpr == null) { orExpr = new OrExpr(); orExpr.add(expr); } AmberExpr andExpr = parseAndExpr(); if (andExpr == null) continue; orExpr.add(andExpr); } return orExpr == null ? expr : orExpr; } /** * Parses an and expression. */ private AmberExpr parseAndExpr() throws QueryParseException { AmberExpr expr = parseNotExpr(); AndExpr andExpr = null; while (peekToken() == AND) { scanToken(); if (andExpr == null) { andExpr = new AndExpr(); andExpr.add(expr); } AmberExpr notExpr = parseNotExpr(); if (notExpr == null) continue; andExpr.add(notExpr); } return andExpr == null ? expr : andExpr; } /** * Parses a NOT expression. * */ private AmberExpr parseNotExpr() throws QueryParseException { AmberExpr expr; if (peekToken() == NOT) { scanToken(); expr = new UnaryExpr(NOT, parseCmpExpr()); } else expr = parseCmpExpr(); // jpa/1199, jpa/119l if (_parsingResult || _parsingHaving) return expr; if (! _isSizeFunExpr) return expr; if (_havingExpr == null) { _havingExpr = expr; } else if (expr != null) { // jpa/1199 _havingExpr = AndExpr.create(_havingExpr, expr); } return null; } /** * Parses a comparison expression. * * <pre> * cmp-expr ::= add-expr '=' add-expr is-term? * ::= add-expr 'NOT'? 'BETWEEN' add-expr 'AND' add-expr is-term? * ::= add-expr 'NOT'? 'LIKE' string ('ESCAPE' string)? is-term? * ::= add-expr 'NOT'? 'IN' ('lit-1', ..., 'lit-n') * ::= add-expr * </pre> * * @return the parsed expression */ private AmberExpr parseCmpExpr() throws QueryParseException { AmberExpr expr = parseConcatExpr(); int token = peekToken(); boolean isNot = false; if (token == NOT) { scanToken(); isNot = true; token = peekToken(); if (token != BETWEEN && token != LIKE && token != MEMBER && token != IN) throw error(L.l("'NOT' is not expected here.")); } if (token >= EQ && token <= GE) { scanToken(); AmberExpr concatExpr = parseConcatExpr(); return parseIs(new BinaryExpr(token, expr, concatExpr)); } else if (token == BETWEEN) { scanToken(); AmberExpr min = parseConcatExpr(); if ((token = scanToken()) != AND) throw error(L.l("Expected 'AND' at {0}", tokenName(token))); AmberExpr max = parseConcatExpr(); // jpa/106a if (! isCompatibleExpression(expr, min)) throw error(L.l("Expected compatible expression at {0} BETWEEN {1}", expr, min)); if (! isCompatibleExpression(expr, max)) throw error(L.l("Expected compatible expression at BETWEEN {0} AND {1}", min, max)); return new BetweenExpr(expr, min, max, isNot); } else if (token == LIKE) { scanToken(); AmberExpr pattern = parseConcatExpr(); // jpa/1075 if (pattern instanceof LiteralExpr) { LiteralExpr literalExpr = (LiteralExpr) pattern; if (literalExpr.getJavaType() != String.class) throw error(L.l("Expected string at {0}", pattern)); } else if (! (pattern instanceof ArgExpr)) // jpa/1076 throw error(L.l("Expected string at {0}", pattern)); String escape = null; if (peekToken() == ESCAPE) { scanToken(); if ((token = scanToken()) != STRING) throw error(L.l("Expected string at {0}", tokenName(token))); escape = _lexeme.toString(); } return parseIs(new LikeExpr(expr, pattern, escape, isNot)); } else if (token == IN) { scanToken(); token = scanToken(); if (token != '(') throw error(L.l("Expected '(' after IN at {0}", tokenName(token))); ArrayList<AmberExpr> args = new ArrayList<AmberExpr>(); while ((token = peekToken()) > 0 && token != ')') { AmberExpr arg = parseExpr(); args.add(arg); token = peekToken(); if (token == ',') { scanToken(); token = peekToken(); } } if (peekToken() != ')') throw error(L.l("Expected ')' after IN at {0}", tokenName(token))); scanToken(); if (expr instanceof IdExpr) { IdExpr idExpr = (IdExpr) expr; // jpa/1174 if (idExpr.getFromItem().isEntityType()) throw error(L.l("Unexpected entity at '{0} IN'", expr)); } return new InExpr(expr, args, isNot); } else if (token == MEMBER) { scanToken(); token = peekToken(); if (token == OF) token = scanToken(); AmberExpr collection = parseExpr(); // jpa/10c8 if (expr instanceof ArgExpr) { addArg((ArgExpr) expr); } else if (! (expr instanceof PathExpr)) throw error(L.l("MEMBER OF requires an entity-valued item.")); if (! isCollectionExpr(collection)) throw error(L.l("MEMBER OF requires an entity-valued collection at '{0}'.", collection.getClass().getName())); return parseIs(MemberExpr.create(this, expr, collection, isNot)); } else return parseIs(expr); } private AmberExpr parseIs(AmberExpr expr) throws QueryParseException { int token = peekToken(); if (token != IS) return expr; scanToken(); boolean isNot = false; token = scanToken(); if (token == NOT) { isNot = true; token = scanToken(); } if (token == NULL) { if (expr instanceof KeyColumnExpr) expr = ((KeyColumnExpr) expr).getParent(); else if (expr instanceof IdExpr) { IdExpr idExpr = (IdExpr) expr; // jpa/1093 if (idExpr.getFromItem().isEntityType()) throw error(L.l("Unexpected entity at '{0} IS'", expr)); } if (isNot) return new UnaryExpr(NOT_NULL, expr); else return new UnaryExpr(NULL, expr); } else if (token == EMPTY) { if (! isCollectionExpr(expr)) throw error(L.l("IS EMPTY requires an entity-valued collection at '{0}'.", expr.getClass().getName())); expr = new EmptyExpr(expr); if (! isNot) expr = new UnaryExpr(NOT, expr); return expr; } else throw error(L.l("expected NULL at '{0}'", tokenName(token))); } /** * Parses a concat expression. */ private AmberExpr parseConcatExpr() throws QueryParseException { AmberExpr expr = parseAddExpr(); while (true) { int token = peekToken(); switch (token) { case CONCAT_OP: scanToken(); ArrayList<AmberExpr> args = new ArrayList<AmberExpr>(); args.add(expr); args.add(parseAddExpr()); expr = ConcatFunExpr.create(this, args); break; default: return expr; } } } /** * Parses an add expression. */ private AmberExpr parseAddExpr() throws QueryParseException { AmberExpr expr = parseMulExpr(); while (true) { int token = peekToken(); switch (token) { case '+': case '-': scanToken(); expr = new BinaryExpr(token, expr, parseMulExpr()); break; default: return expr; } } } /** * Parses a mul expression. */ private AmberExpr parseMulExpr() throws QueryParseException { AmberExpr expr = parseTerm(); while (true) { int token = peekToken(); switch (token) { case '*': case '/': scanToken(); expr = new BinaryExpr(token, expr, parseTerm()); break; default: return expr; } } } /** * Parses a term * * <pre> * term ::= - term * ::= + term * ::= NOT term * </pre> */ private AmberExpr parseTerm() throws QueryParseException { int token = peekToken(); switch (token) { case '+': case '-': case NOT: scanToken(); return new UnaryExpr(token, parseTerm()); default: return parseSimpleTerm(); } } /** * Parses a simple term * * <pre> * term ::= INTEGER | LONG | DOUBLE | STRING * ::= THIS * ::= IDENTIFIER * ::= IDENTIFIER '(' args ')' * ::= '(' expr ')' * </pre> */ private AmberExpr parseSimpleTerm() throws QueryParseException { int token = scanToken(); switch (token) { case IDENTIFIER: case LOCATE: case LENGTH: case MAX: case MIN: case SUM: case ABS: case SQRT: case MOD: case SIZE: case CONCAT: case LOWER: case UPPER: case SUBSTRING: case TRIM: { String name = _lexeme.toString(); if (peekToken() != '(') { // Either IdExpr or EmbeddedExpr AbstractPathExpr tableExpr = getIdentifier(name); if (tableExpr == null) { // jpa/0w22 tableExpr = getEmbeddedAlias(name); } if (tableExpr == null) { // jpa/11z6 AmberExpr amberExpr = parseEnum(name); if (amberExpr != null) return amberExpr; } if (tableExpr != null) { AmberExpr amberExpr = parsePath(tableExpr); return amberExpr; } if (_query.getFromList().size() == 0) throw error(L.l("Expected a FROM clause before '{0}'", name)); FromItem fromItem = _query.getFromList().get(0); tableExpr = fromItem.getIdExpr(); AmberExpr next = tableExpr.createField(this, name); if (next instanceof PathExpr) return addPath((PathExpr) next); else if (next != null) return next; throw error(L.l("'{0}' is an unknown table or column", name)); } else { name = name.toLowerCase(Locale.ENGLISH); // EXISTS | ALL | ANY | SOME if (name.equals("exists") || name.equals("all") || name.equals("any") || name.equals("some")) { scanToken(); if (peekToken() != SELECT && peekToken() != FROM) throw error(L.l(name.toUpperCase(Locale.ENGLISH) + " requires '(SELECT'")); AmberSelectQuery select = parseSelect(true); if (peekToken() != ')') throw error(L.l(name.toUpperCase(Locale.ENGLISH) + " requires ')'")); scanToken(); ArrayList<FromItem> parentFromList; parentFromList = select.getParentQuery().getFromList(); // jpa/1178 select.getFromList().addAll(0, parentFromList); if (name.equals("exists")) return new ExistsExpr(select); else if (name.equals("all")) return new AllExpr(select); else // SOME is a synonymous with ANY return new AnyExpr(select); } else { return parseFunction(name, token); } } } case CURRENT_DATE: case CURRENT_TIME: case CURRENT_TIMESTAMP: { String name = _lexeme.toString(); return parseFunction(name, token); } case FALSE: return new LiteralExpr(this, _lexeme, boolean.class); case TRUE: return new LiteralExpr(this, _lexeme, boolean.class); case NULL: return new NullExpr(); case INTEGER: return new LiteralExpr(this, _lexeme, int.class); case LONG: return new LiteralExpr(this, _lexeme, long.class); case DOUBLE: return new LiteralExpr(this, _lexeme, double.class); case STRING: return new LiteralExpr(this, _lexeme, String.class); case ARG: { ArgExpr arg = new ArgExpr(this, Integer.parseInt(_lexeme)); /* if (_addArgToQuery) addArg(arg); */ return arg; } case NAMED_ARG: { ArgExpr arg = new ArgExpr(this, _lexeme, _parameterCount); return arg; } /* case THIS: { if (_thisExpr == null) { _thisExpr = new IdExpr(this, "caucho_this", _bean); addFromItem("caucho_this", _bean.getSQLTable()); _argList.add(0, new ThisExpr(this, _bean)); } return _thisExpr; } */ case '(': AmberExpr expr = parseExpr(); if ((token = scanToken()) != ')') throw error(L.l("expected `)' at {0}", tokenName(token))); return expr; default: throw error(L.l("expected term at {0}", tokenName(token))); } } /** * Parses a path * * <pre> * path ::= IDENTIFIER * ::= path . IDENTIFIER * </pre> */ private AmberExpr parsePath(PathExpr path) throws QueryParseException { while (peekToken() == '.') { scanToken(); String field = parseIdentifier(); AmberExpr next = path.createField(this, field); if (next == null) throw error(L.l("'{0}' does not have a field '{1}'", path, field)); if (! (next instanceof PathExpr)) return next; PathExpr nextPath = addPath((PathExpr) next); if (peekToken() == '[') { scanToken(); AmberExpr index = parseExpr(); next = nextPath.createArray(index); if (next == null) throw error(L.l("'{0}' does not have a map field '{1}'", path, field)); if (peekToken() != ']') { throw error(L.l("expected ']' at '{0}'", tokenName(peekToken()))); } scanToken(); } if (next instanceof PathExpr) path = addPath((PathExpr) next); else return next; } return path; } /** * Parses a enum value * * <pre> * enum ::= (IDENTIFIER '.')+ IDENTIFIER * </pre> */ private EnumExpr parseEnum(String head) throws QueryParseException { CharBuffer cb = CharBuffer.allocate(); int token; while ((token = scanToken()) == '.') { if (cb.length() > 0) cb.append('.'); cb.append(head); token = scanToken(); if (token != IDENTIFIER) throw error(L.l("expected identifier for enumerated type {0} at {1}", cb.toString(), tokenName(token))); head = _lexeme.toString(); } int value = -1; Class cl = null; try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); cl = Class.forName(cb.toString(), false, loader); Enum enumValue = Enum.valueOf(cl, head); value = enumValue.ordinal(); } catch (ClassNotFoundException e) { // Not an error; only this is not a enum. // Continue - see parseSimpleTerm(). return null; } return new EnumExpr(cl, head, value); } /** * Parses a function * * <pre> * fun ::= IDENTIFIER ( expr* ) * ::= IDENTIFIER ( DISTINCT expr* ) * </pre> */ private AmberExpr parseFunction(String id, int functionToken) throws QueryParseException { // Function with no arguments. switch (functionToken) { case CURRENT_DATE: return CurrentDateFunExpr.create(this); case CURRENT_TIME: return CurrentTimeFunExpr.create(this); case CURRENT_TIMESTAMP: return CurrentTimestampFunExpr.create(this); } // Function with arguments. scanToken(); // Example: "'c'" AmberExpr trimChar = null; TrimFunExpr.TrimSemantics trimSemantics = TrimFunExpr.TrimSemantics.BOTH; boolean distinct = false; ArrayList<AmberExpr> args = new ArrayList<AmberExpr>(); if (functionToken == TRIM) { switch (peekToken()) { case LEADING: trimSemantics = TrimFunExpr.TrimSemantics.LEADING; scanToken(); break; case TRAILING: trimSemantics = TrimFunExpr.TrimSemantics.TRAILING; scanToken(); break; case BOTH: scanToken(); break; // default: [BOTH], but no scanToken(). } AmberExpr arg = null; if (peekToken() != FROM) { arg = parseExpr(); if (arg instanceof LiteralExpr) { String v = ((LiteralExpr) arg).getValue(); if (v.length() != 3) // "'c'" throw error(L.l("expected a single char expression for TRIM at {0}", v)); } } if (peekToken() == FROM) { scanToken(); trimChar = arg; arg = parseExpr(); } args.add(arg); } else { if (peekToken() == DISTINCT) { distinct = true; scanToken(); } while ((peekToken() >= 0) && (peekToken() != ')')) { AmberExpr arg = parseExpr(); if (id.equalsIgnoreCase("object")) { if (arg instanceof PathExpr) { PathExpr pathExpr = (PathExpr) arg; arg = LoadExpr.create(pathExpr); arg = arg.bindSelect(this); int token = scanToken(); if (token != ')') throw error(L.l("expected ')' at '{0}'", tokenName(token))); return arg; } } args.add(arg); if (peekToken() != ',') break; scanToken(); } } if (peekToken() != ')') throw error(L.l("expected ')' at '{0}'", tokenName(scanToken()))); scanToken(); FunExpr funExpr; switch (functionToken) { case LOCATE: funExpr = LocateFunExpr.create(this, args); break; case LENGTH: funExpr = LengthFunExpr.create(this, args); break; case MAX: funExpr = MaxFunExpr.create(this, id, args, distinct); break; case MIN: funExpr = MinFunExpr.create(this, id, args, distinct); break; case SUM: funExpr = SumFunExpr.create(this, id, args, distinct); break; case ABS: funExpr = AbsFunExpr.create(this, args); break; case SQRT: funExpr = SqrtFunExpr.create(this, args); break; case MOD: funExpr = ModFunExpr.create(this, args); break; case SIZE: if (! (_query instanceof AmberSelectQuery)) throw error(L.l("The SIZE() function is only supported for SELECT or subselect queries")); // jpa/119l AmberExpr arg = args.get(0); if (arg instanceof ManyToOneExpr) { // @ManyToMany arg = ((ManyToOneExpr) arg).getParent(); } if (! (arg instanceof OneToManyExpr)) throw error(L.l("The SIZE() function is only supported for @ManyToMany or @OneToMany relationships. The argument '{0}' is not supported.", args.get(0))); OneToManyExpr oneToMany = (OneToManyExpr) arg; _groupList = new ArrayList<AmberExpr>(); LinkColumns linkColumns = oneToMany.getLinkColumns(); ForeignColumn fkColumn = linkColumns.getColumns().get(0); AmberExpr groupExpr = oneToMany.getParent(); if (groupExpr instanceof PathExpr) { // jpa/119n PathExpr pathExpr = (PathExpr) groupExpr; groupExpr = LoadExpr.create(pathExpr); groupExpr = groupExpr.bindSelect(this); } // groupExpr = new ColumnExpr(oneToMany.getParent(), // fkColumn.getTargetColumn()); _groupList.add(groupExpr); ((AmberSelectQuery) _query).setGroupList(_groupList); funExpr = SizeFunExpr.create(this, args); // jpa/1199, jpa/119l if (! _parsingResult) { if (_query instanceof AmberSelectQuery) { AmberSelectQuery query = (AmberSelectQuery) _query; ArrayList<AmberExpr> resultList = query.getResultList(); for (AmberExpr expr : resultList) { if (expr instanceof SizeFunExpr) { SizeFunExpr sizeFun = (SizeFunExpr) expr; AmberExpr amberExpr = sizeFun.getArgs().get(0); // @ManyToMany if (amberExpr instanceof ManyToOneExpr) { amberExpr = ((ManyToOneExpr) amberExpr).getParent(); } if (amberExpr.equals(arg)) args.set(0, amberExpr); } } } if (_appendResultList == null) _appendResultList = new ArrayList<AmberExpr>(); _appendResultList.add(funExpr.bindSelect(this)); _isSizeFunExpr = true; } break; case CONCAT: funExpr = ConcatFunExpr.create(this, args); break; case LOWER: funExpr = LowerFunExpr.create(this, args); break; case UPPER: funExpr = UpperFunExpr.create(this, args); break; case SUBSTRING: funExpr = SubstringFunExpr.create(this, args); break; case TRIM: { TrimFunExpr trimFunExpr = TrimFunExpr.create(this, args); trimFunExpr.setTrimChar(trimChar); trimFunExpr.setTrimSemantics(trimSemantics); funExpr = trimFunExpr; break; } default: funExpr = FunExpr.create(this, id, args, distinct); } return funExpr; } /** * Returns the matching identifier. */ private IdExpr getIdentifier(String name) throws QueryParseException { AbstractQuery query = _query; for (; query != null; query = query.getParentQuery()) { ArrayList<FromItem> fromList = query.getFromList(); for (int i = 0; i < fromList.size(); i++) { FromItem from = fromList.get(i); if (from.getName().equalsIgnoreCase(name)) return from.getIdExpr(); } } return null; // throw error(L.l("`{0}' is an unknown table", name)); } /** * Returns the matching embedded alias. */ private EmbeddedExpr getEmbeddedAlias(String name) throws QueryParseException { // jpa/0w22 AbstractQuery query = _query; for (; query != null; query = query.getParentQuery()) { HashMap<String, EmbeddedExpr> embeddedAliases = query.getEmbeddedAliases(); for (Map.Entry<String, EmbeddedExpr> entry : embeddedAliases.entrySet()) { if (entry.getKey().equalsIgnoreCase(name)) return entry.getValue(); } } return null; } /** * Returns true if expr is a collection. */ private boolean isCollectionExpr(AmberExpr expr) { // jpa/10a2 // ManyToMany is implemented as a // ManyToOne[embeddeding OneToMany] if ((expr instanceof ManyToOneExpr) && (((ManyToOneExpr) expr).getParent() instanceof OneToManyExpr)) return true; else if (expr instanceof OneToManyExpr) return true; else if (expr instanceof CollectionIdExpr) return true; return false; } /** * Returns true if expr1 and expr2 are compatible. */ private boolean isCompatibleExpression(AmberExpr expr1, AmberExpr expr2) { // XXX: jpa/106a if (expr1 instanceof LiteralExpr) { if (expr2 instanceof LiteralExpr) { Class javaType1 = ((LiteralExpr) expr1).getJavaType(); Class javaType2 = ((LiteralExpr) expr2).getJavaType(); if (javaType1.isAssignableFrom(javaType2)) return true; return false; } } return true; } /** * Parses an identifier. */ private String parseIdentifier() throws QueryParseException { int token = scanToken(); String identifier = _lexeme; // Resolves ambiguous identifiers: // 1. 'order': "SELECT o FROM Order o" if (token == ORDER) { int parseIndex = _parseIndex; scanToken(); if (peekToken() != BY) { token = IDENTIFIER; // Restores parse index right after ORDER BY. _parseIndex = parseIndex; _lexeme = identifier; _token = -1; } } // 2. 'member': "SELECT m FROM Member m" (jpa/0x02) else if (_parsingFrom && token == MEMBER) { token = IDENTIFIER; } if (token != IDENTIFIER) { throw error(L.l("expected identifier at `{0}'", tokenName(token))); } return identifier; } /** * Peeks the next token * * @return integer code for the token */ private int peekToken() throws QueryParseException { if (_token > 0) return _token; _token = scanToken(); return _token; } /** * Scan the next token. If the lexeme is a string, its string * representation is in "lexeme". * * @return integer code for the token */ private int scanToken() throws QueryParseException { if (_token > 0) { int value = _token; _token = -1; return value; } int sign = 1; int ch; for (ch = read(); Character.isWhitespace((char) ch); ch = read()) { } switch (ch) { case -1: case '.': case '*': case '/': case ',': case '+': case '-': case '[': case ']': return ch; case '(': _depth++; return ch; case ')': _depth--; return ch; case '=': if ((ch = read()) == '>') return EXTERNAL_DOT; else { unread(ch); return EQ; } case '!': if ((ch = read()) == '=') return NE; else { unread(ch); return '!'; } case '<': if ((ch = read()) == '=') return LE; else if (ch == '>') return NE; else { unread(ch); return LT; } case '>': if ((ch = read()) == '=') return GE; else { unread(ch); return GT; } case '?': CharBuffer cb = CharBuffer.allocate(); int index = 0; for (ch = read(); ch >= '0' && ch <= '9'; ch = read()) { cb.append((char) ch); index = 10 * index + ch - '0'; } unread(ch); _lexeme = cb.close(); if (_lexeme.length() == 0) { _lexeme = String.valueOf(++_parameterCount); } else if (index <= 0) throw error(L.l("`{0}' must refer to a positive argument", "?" + _lexeme)); return ARG; case ':': if (Character.isJavaIdentifierStart((char) (ch = read()))) { cb = CharBuffer.allocate(); for (; ch > 0 && Character.isJavaIdentifierPart((char) ch); ch = read()) cb.append((char) ch); unread(ch); _lexeme = cb.close(); _parameterCount++; } else throw error(L.l("`{0}' must be a valid parameter identifier", ":" + ((char) ch))); return NAMED_ARG; case '|': if ((ch = read()) == '|') return CONCAT_OP; else throw error(L.l("unexpected char at {0}", String.valueOf((char) ch))); // @@ is useless? case '@': if ((ch = read()) != '@') throw error(L.l("`@' expected at {0}", charName(ch))); return scanToken(); } if (Character.isJavaIdentifierStart((char) ch)) { CharBuffer cb = CharBuffer.allocate(); for (; ch > 0 && Character.isJavaIdentifierPart((char) ch); ch = read()) cb.append((char) ch); unread(ch); _lexeme = cb.close(); String lower = _lexeme.toLowerCase(Locale.ENGLISH); int token = _reserved.get(lower); if (token > 0) return token; else return IDENTIFIER; } else if (ch >= '0' && ch <= '9') { CharBuffer cb = CharBuffer.allocate(); int type = INTEGER; if (sign < 0) cb.append('-'); for (; ch >= '0' && ch <= '9'; ch = read()) cb.append((char) ch); if (ch == '.') { type = DOUBLE; cb.append('.'); for (ch = read(); ch >= '0' && ch <= '9'; ch = read()) cb.append((char) ch); } if (ch == 'e' || ch == 'E') { type = DOUBLE; cb.append('e'); if ((ch = read()) == '+' || ch == '-') { cb.append((char) ch); ch = read(); } if (! (ch >= '0' && ch <= '9')) throw error(L.l("exponent needs digits at {0}", charName(ch))); for (; ch >= '0' && ch <= '9'; ch = read()) cb.append((char) ch); } if (ch == 'F' || ch == 'D') type = DOUBLE; else if (ch == 'L') { type = LONG; } else unread(ch); _lexeme = cb.close(); return type; } else if (ch == '\'') { CharBuffer cb = CharBuffer.allocate(); cb.append("'"); for (ch = read(); ch >= 0; ch = read()) { if (ch == '\'') { if ((ch = read()) == '\'') cb.append("''"); else { unread(ch); break; } } else cb.append((char) ch); } cb.append("'"); _lexeme = cb.close(); return STRING; } throw error(L.l("unexpected char at {0}", "" + (char) ch)); } /** * Returns the next character. */ private int read() { if (_parseIndex < _sql.length()) return _sql.charAt(_parseIndex++); else return -1; } /** * Unread the last character. */ private void unread(int ch) { if (ch >= 0) _parseIndex--; } /** * Returns the jdbc meta data, if available. */ private JdbcMetaData getMetaData() { if (_persistenceUnit == null) return null; return _persistenceUnit.getMetaData(); } /** * Creates an error. */ public QueryParseException error(String msg) { msg += "\nin \"" + _sql + "\""; return new QueryParseException(msg); } /** * Returns the name for a character */ private String charName(int ch) { if (ch < 0) return L.l("end of query"); else return String.valueOf((char) ch); } /** * Returns the name of a token */ private String tokenName(int token) { switch (token) { case AS: return "AS"; case FROM: return "FROM"; case IN: return "IN"; case SELECT: return "SELECT"; case WHERE: return "WHERE"; case OR: return "OR"; case AND: return "AND"; case NOT: return "NOT"; case BETWEEN: return "BETWEEN"; case THIS: return "THIS"; case TRUE: return "FALSE"; case EMPTY: return "EMPTY"; case MEMBER: return "MEMBER"; case OF: return "OF"; case NULL: return "NULL"; case ORDER: return "ORDER"; case BY: return "BY"; case ASC: return "ASC"; case DESC: return "DESC"; case LIMIT: return "LIMIT"; case EXTERNAL_DOT: return "=>"; case -1: return L.l("end of query"); default: if (token < 128) return "'" + String.valueOf((char) token) + "'"; else return "'" + _lexeme + "'"; } } /** * Returns a debuggable description of the select. */ public String toString() { return "QueryParser[]"; } static { _reserved = new IntMap(); _reserved.put("as", AS); _reserved.put("from", FROM); _reserved.put("in", IN); _reserved.put("select", SELECT); _reserved.put("update", UPDATE); _reserved.put("delete", DELETE); _reserved.put("set", SET); _reserved.put("distinct", DISTINCT); _reserved.put("where", WHERE); _reserved.put("order", ORDER); _reserved.put("group", GROUP); _reserved.put("by", BY); _reserved.put("having", HAVING); _reserved.put("asc", ASC); _reserved.put("desc", DESC); _reserved.put("limit", LIMIT); _reserved.put("offset", OFFSET); _reserved.put("join", JOIN); _reserved.put("inner", INNER); _reserved.put("left", LEFT); _reserved.put("outer", OUTER); _reserved.put("fetch", FETCH); _reserved.put("or", OR); _reserved.put("and", AND); _reserved.put("not", NOT); _reserved.put("length", LENGTH); _reserved.put("locate", LOCATE); _reserved.put("abs", ABS); _reserved.put("sqrt", SQRT); _reserved.put("mod", MOD); _reserved.put("size", SIZE); _reserved.put("max", MAX); _reserved.put("min", MIN); _reserved.put("sum", SUM); _reserved.put("concat", CONCAT); _reserved.put("lower", LOWER); _reserved.put("upper", UPPER); _reserved.put("substring", SUBSTRING); _reserved.put("trim", TRIM); _reserved.put("both", BOTH); _reserved.put("leading", LEADING); _reserved.put("trailing", TRAILING); _reserved.put("current_date", CURRENT_DATE); _reserved.put("current_time", CURRENT_TIME); _reserved.put("current_timestamp", CURRENT_TIMESTAMP); _reserved.put("between", BETWEEN); _reserved.put("like", LIKE); _reserved.put("escape", ESCAPE); _reserved.put("is", IS); _reserved.put("new", NEW); _reserved.put("this", THIS); _reserved.put("true", TRUE); _reserved.put("false", FALSE); _reserved.put("unknown", UNKNOWN); _reserved.put("empty", EMPTY); _reserved.put("member", MEMBER); _reserved.put("of", OF); _reserved.put("null", NULL); } }