All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.caucho.amber.query.QueryParser Maven / Gradle / Ivy

/*
 * Copyright (c) 1998-2018 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 _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 _pathMap
    = new HashMap();

  // 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 _argList = new ArrayList();

  private HashMap _joinFetchMap;

  ArrayList _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 _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();
  }

  /**
   * Generates a new arg.
   */
  public int generateSQLArg()
  {
    return _sqlArgCount++;
  }

  /**
   * Parses the query.
   */
  public AbstractQuery parse()
    throws AmberException
  {
    /*
      _query = query;
      _fromList = new ArrayList();
      _pathMap = new HashMap();
      _idMap = new HashMap();
      _argList = new ArrayList();
      _linkList = new ArrayList();
    */

    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 resultList = new ArrayList();

    _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 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 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 groupList = _groupList;

    token = peekToken();
    if (token == GROUP) {
      scanToken();

      if (peekToken() == BY) {
        scanToken();
        hasGroupBy = true;
      }

      if (groupList == null)
        groupList = new ArrayList();

      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 orderList = new ArrayList();
      ArrayList ascList = new ArrayList();

      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 fields = new ArrayList();
    ArrayList values = new ArrayList();

    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
  {
    AmberDeleteQuery query = new AmberDeleteQuery(_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 fields,
                              ArrayList 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.
   *
   * 
   * from-item ::= schema AS? IDENTIFIER
   * 
*/ 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. * *
   * 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
   * 
* * @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 args = new ArrayList(); 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 args = new ArrayList(); 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 * *
   * term ::= - term
   *      ::= + term
   *      ::= NOT term
   * 
*/ 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 * *
   * term ::= INTEGER | LONG | DOUBLE | STRING
   *      ::= THIS
   *      ::= IDENTIFIER
   *      ::= IDENTIFIER '(' args ')'
   *      ::= '(' expr ')'
   * 
*/ 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 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 * *
   * path ::= IDENTIFIER
   *      ::= path . IDENTIFIER
   * 
*/ 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 * *
   * enum ::= (IDENTIFIER '.')+ IDENTIFIER
   * 
*/ 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 * *
   * fun ::= IDENTIFIER ( expr* )
   *     ::= IDENTIFIER ( DISTINCT expr* )
   * 
*/ 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 args = new ArrayList(); 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(); 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 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(); _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 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 embeddedAliases = query.getEmbeddedAliases(); for (Map.Entry 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); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy