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

com.caucho.v5.kraken.query.QueryParserKraken Maven / Gradle / Ivy

There is a newer version: 1.0.1
Show newest version
/*
 * Copyright (c) 1998-2015 Caucho Technology -- all rights reserved
 *
 * This file is part of Baratine(TM)
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Baratine 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.
 *
 * Baratine 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 Baratine; 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.v5.kraken.query;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.caucho.v5.bartender.BartenderSystem;
import com.caucho.v5.kelp.Column;
import com.caucho.v5.kelp.TableKelp;
import com.caucho.v5.kelp.query.QueryBuilderKelp;
import com.caucho.v5.kraken.table.TableKraken;
import com.caucho.v5.kraken.table.TableManagerKraken;
import com.caucho.v5.util.CharBuffer;
import com.caucho.v5.util.L10N;
import com.caucho.v5.util.ModulePrivate;

@ModulePrivate
public class QueryParserKraken {
  private static final Logger log
    = Logger.getLogger(QueryParserKraken.class.getName());
  private static final L10N L = new L10N(QueryParserKraken.class);

  // private final static IntMap _reserved;
  private final static HashMap _reserved;

  private final String _sql;
  private final char []_sqlChars;
  private final int _sqlLength;

  private int _parseIndex;

  private final CharBuffer _cb = new CharBuffer();

  private String _lexeme;
  private Token _token;
  
  private String _podName;

  private ArrayList _params = new ArrayList<>();

  private QueryBuilderKraken _query;
  //private AndExpr _andExpr;
  
  private QueryBuilderKelp _queryBuilderKelp;
  private TableManagerKraken _tableManager;
  
  //private SerializerFactory _serializerFactory;
  //private OutFactoryH3 _serializerFactory;

  public QueryParserKraken(TableManagerKraken tableManager,
                            String sql)
  {
    _tableManager = tableManager;
    _sql = sql;
    _sqlLength = _sql.length();
    _sqlChars = new char[_sqlLength];
    _sql.getChars(0, _sqlLength, _sqlChars, 0);
    
    /*
    // XXX: needs to be in an owner
    _serializerFactory = new SerializerFactory();
    _serializerFactory.setAllowNonSerializable(true);
    */
  }
  
  public static QueryBuilderKraken parse(TableManagerKraken tableManager,
                                         String tableName,
                                         String sql)
  {
    QueryParserKraken parser = new QueryParserKraken(tableManager, sql);
    
    String podName = getPodName(tableName);
    
    parser.setPodName(podName);

    return parser.parse();
  }

  public static QueryBuilderKraken parse(TableManagerKraken tableManager,
                                         String sql)
  {
    QueryParserKraken parser = new QueryParserKraken(tableManager, sql);

    return parser.parse();

    /*
    queryBuilder.bind();
    
    return queryBuilder.build(tableManager, db);
    */
  }

  public static String parse(TableManagerKraken tableManager,
                             String tableName, 
                             String columnName, 
                             String query)
  {
    QueryParserKraken parser = new QueryParserKraken(tableManager, query);
    
    ExprKraken expr = parser.parseSubQuery();
    
    String subExpr = expr.toObjectExpr(columnName);

    return subExpr;
  }

  public void setPodName(String podName)
  {
    _podName = podName;
  }

  public TableManagerKraken tableManager()
  {
    return _tableManager;
  }
  
  /*
  public SerializerFactory serializerFactory()
  {
    return _serializerFactory;
  }
  */
  /*
  public OutFactoryH3 serializerFactory()
  {
    return _serializerFactory;
  }
  */

  /**
   * Parses the query.
   */
  public QueryBuilderKraken parse()
  {
    Token token = scanToken();

    switch (token) {
    case CREATE:
      return parseCreate();
      
    case EXPLAIN:
      return parseExplain();

    case INSERT:
      return parseInsert();

    case REPLACE:
      return parseReplace();
      
    case SELECT:
      return parseSelect();
      
    case SELECT_LOCAL:
      return parseSelectLocal();
      
    case MAP:
      return parseMap();
      
    case SHOW:
      return parseShow();
      
    case UPDATE:
      return parseUpdate();
      
    case DELETE:
      return parseDelete();
      
    case WATCH:
      return parseWatch();
      
      /*
    case NOTIFY:
      return parseNotify();
      */

      /*
    case VALIDATE:
      return parseValidate();

    case DROP:
      return parseDrop();
*/
    default:
      throw error("unknown query at {0}", token);
    }
  }

  /**
   * Parses the query.
   */
  private ExprKraken parseSubQuery()
  {
    return parseExpr();
  }

  /**
   * Parses the select.
   */
  private QueryBuilderKraken parseExplain()
  {
    Token token = scanToken();
    
    if (token != Token.SELECT) {
      throw error("Explain requires SELECT at {0}", token);
    }
    
    return parseSelect(new QueryBuilderExplain(_tableManager, _sql));
  }

  /**
   * Parses the select.
   */
  private QueryBuilderKraken parseSelect()
  {
    SelectQueryBuilder selectBuilder
      = new SelectQueryBuilder(_tableManager, _sql);
    
    return parseSelect(selectBuilder);
  }
  
  private QueryBuilderKraken parseSelectLocal()
  {
    SelectQueryBuilder selectBuilder
      = new SelectQueryBuilder(_tableManager, _sql);
    
    selectBuilder.setLocal(true);
    
    return parseSelect(selectBuilder);
  }

  /**
   * Parses the map.
   */
  private QueryBuilderKraken parseMap()
  {
    return parseMap(new MapQueryBuilder(_tableManager, _sql));
  }

  /**
   * Parses the select.
   */
  private QueryBuilderKraken parseMap(QueryBuilderKraken query)
  {
    boolean distinct = false;

    Token token = scanToken();

    if (token == Token.DISTINCT)
      distinct = true;
    else
      _token = token;

    ArrayList resultItems = new ArrayList<>();

    Token startToken = scanToken();
    String startLexeme = _lexeme;
    int startOffset = _parseIndex;

    while ((token = scanToken()) != null
        && token != Token.EOF 
        && token != Token.FROM) {
    }

    if (token != Token.FROM) { 
      throw error("expected FROM at '{0}'", token);
    }

    _query = query;

    String tableName = parseFrom();

    query.setTableName(tableName);
    
    token = scanToken();
    
    Token tailToken = token;
    int tailOffset = _parseIndex;

    _token = startToken;
    _parseIndex = startOffset;
    _lexeme = startLexeme;

    ExprKraken expr = parseSelectExpr();

    resultItems.add(expr);

    while ((token = scanToken()) == Token.COMMA) {
      expr = parseSelectExpr();

      resultItems.add(expr);
    }

    _token = tailToken;
    _parseIndex = tailOffset;

    token = scanToken();
    
    ExprKraken whereExpr = null;

    if (token == Token.WHERE) {
      // _andExpr.add(parseExpr());
      whereExpr = parseExpr();
    }
    else {
      _token = token;
    }

    ParamExpr []params = _params.toArray(new ParamExpr[_params.size()]);
    
    query.setWhereExpr(whereExpr);
    // query.setKelpExpr(whereKelp);
    query.setParams(params);

    for (int i = resultItems.size() - 1; i >= 0; i--) {
      ExprKraken subExpr = resultItems.get(i);
    }

    ExprKraken []resultArray
      = resultItems.toArray(new ExprKraken[resultItems.size()]);

    query.setResults(resultArray);
    
    token = scanToken();
    
    if (token != null 
        && token != Token.EOF
        && token != Token.LIMIT 
        && token != Token.OFFSET) {
      throw error("unexpected token at end '{0}'", token);
    }

    return query;
  }

  /**
   * Parses the select.
   */
  private QueryBuilderKraken parseSelect(QueryBuilderKraken query)
  {
    boolean distinct = false;

    Token token = scanToken();

    if (token == Token.DISTINCT)
      distinct = true;
    else
      _token = token;

    ArrayList resultItems = new ArrayList<>();

    Token startToken = scanToken();
    String startLexeme = _lexeme;
    int startOffset = _parseIndex;

    while ((token = scanToken()) != null
        && token != Token.EOF 
        && token != Token.FROM) {
    }

    if (token != Token.FROM) { 
      throw error("expected FROM at '{0}'", token);
    }

    _query = query;

    String tableName = parseFrom();

    query.setTableName(tableName);
    
    token = scanToken();
    
    Token tailToken = token;
    int tailOffset = _parseIndex;

    _token = startToken;
    _parseIndex = startOffset;
    _lexeme = startLexeme;

    ExprKraken expr = parseSelectExpr();

    resultItems.add(expr);

    while ((token = scanToken()) == Token.COMMA) {
      expr = parseSelectExpr();

      resultItems.add(expr);
    }

    _token = tailToken;
    _parseIndex = tailOffset;

    token = scanToken();
    
    ExprKraken whereExpr = null;

    if (token == Token.WHERE) {
      // _andExpr.add(parseExpr());
      whereExpr = parseExpr();
    }
    else {
      _token = token;
    }
    
    /*
    QueryKelp whereKelp = null;
    
    if (whereExpr != null) {
      QueryBuilderKelp queryBuilderKelp = new QueryBuilderKelp(table);
      
      ExprBuilderKelp whereKelpBuilder = whereExpr.buildKelp(queryBuilderKelp);
      
      if (whereKelpBuilder != null) {
        whereKelp = queryBuilderKelp.build(whereKelpBuilder);
      }
    }
    */

    ParamExpr []params = _params.toArray(new ParamExpr[_params.size()]);

    //Expr whereExpr = _andExpr.getSingleExpr();
    //_andExpr = null;
    
    query.setWhereExpr(whereExpr);
    // query.setKelpExpr(whereKelp);
    query.setParams(params);

    for (int i = resultItems.size() - 1; i >= 0; i--) {
      ExprKraken subExpr = resultItems.get(i);

      /*
      if (subExpr instanceof UnboundStarExpr) {
        UnboundStarExpr unboundExpr = (UnboundStarExpr) subExpr;
        ArrayList exprList = unboundExpr.expand(query.getFromItems());

        resultItems.remove(i);
        resultItems.addAll(i, exprList);
      }
      */
    }

    /*
    ArrayList groupItems = null;
    token = scanToken();
    if (token == GROUP) {
      token = scanToken();

      if (token != BY)
        throw error(L.l("expected BY at `{0}'", tokenName(token)));

      groupItems = parseGroup(query);
    }
    else
      _token = token;
      */

    /*
    token = scanToken();
    if (token == ORDER) {
      token = scanToken();

      if (token != BY)
        throw error(L.l("expected BY at `{0}'", tokenName(token)));

      Order order = parseOrder(query, resultItems);
    }
    else
      _token = token;
      */

    ExprKraken []resultArray
      = resultItems.toArray(new ExprKraken[resultItems.size()]);

    query.setResults(resultArray);

    /*
    if (query.isGroup()) {
      Expr []resultList = query.getResults();

      bindGroup(query, groupItems);

      for (int i = 0; i < resultList.length; i++) {
        Expr subExpr = resultList[i];

        if (! (subExpr instanceof GroupExpr)) {
          resultList[i] = new GroupResultExpr(i, subExpr);
        }
      }
    }
    */
    
    /*
    token = scanToken();
    if (token == Token.LIMIT) {
      parseLimit(query);
    }
    else {
      _token = token;
    }
    */
    
    token = scanToken();
    
    if (token != null 
        && token != Token.EOF
        && token != Token.LIMIT 
        && token != Token.OFFSET) {
      throw error("unexpected token at end '{0}'", token);
    }

    // _query = query.getParent();
    // _andExpr = oldAnd;

    return query;
  }

  /**
   * Parses the show.
   */
  private QueryBuilderShow parseShow()
  {
    return parseShow(new QueryBuilderShow(_tableManager, _sql));
  }

  /**
   * Parses the show.
   */
  private QueryBuilderShow parseShow(QueryBuilderShow query)
  {
    Token token = scanToken();

    if (token == Token.TABLE) {
    }
    else if (token == Token.IDENTIFIER
             && _lexeme.equalsIgnoreCase("tableinfo")) {
      query.method("tableinfo");
    }
    else {
      throw error("Expected TABLE at {0}", token);
    }
    
    token = scanToken();

    if (token != Token.IDENTIFIER) {
      throw error("Expected IDENTIFIER at {0}", token);
    }
    
    String pod = _lexeme;
    String name;
    
    if (peekToken() == Token.DOT) {
      scanToken();

      if ((token = scanToken()) != Token.IDENTIFIER) {
        throw error("Expected IDENTIFIER at {0}", token);
      }
      
      name = _lexeme; 
    }
    else {
      name = pod;
      pod = getPodName();
    }
    
    query.setTableName(pod + '.' + name);

    return query;
  }

  /*
  private ArrayList parseFromItems()
    throws SQLException
  {
    ArrayList fromItems = new ArrayList();

    int token;

    // XXX: somewhat hacked syntax
    while ((token = scanToken()) == '(') {
    }
    _token = token;
    
    FromItem fromItem = parseFromItem();

    if (fromItem != null)
      fromItems.add(fromItem);
    
    int parenCount = 0;

    while (true) {
      token = scanToken();

      boolean isNatural = false;
      boolean isOuter = false;
      boolean isLeft = true;
      boolean isRight = true;

      if (token == ',') {
        fromItem = parseFromItem();
        fromItems.add(fromItem);
        continue;
      }
      else if (token == '(') {
        parenCount++;
        continue;
      }
      else if (token == ')') {
        if (--parenCount < 0) {
          _token = token;
          break;
        }
        else
          continue;
      }
      else if (token != IDENTIFIER) {
        _token = token;
        break;
      }
      else if ("join".equalsIgnoreCase(_lexeme)) {
      }
      else if ("inner".equalsIgnoreCase(_lexeme)) {
        String join = parseIdentifier();

        if (! "join".equalsIgnoreCase(join))
          throw error(L.l("expected JOIN at '{0}'", join));
      }
      else if ("left".equalsIgnoreCase(_lexeme)) {
        String name = parseIdentifier();

        if ("outer".equalsIgnoreCase(name))
          name = parseIdentifier();

        if (! "join".equalsIgnoreCase(name))
          throw error(L.l("expected JOIN at '{0}'", name));

        isOuter = true;
      }
      else if ("right".equalsIgnoreCase(_lexeme)) {
        String name = parseIdentifier();

        if ("outer".equalsIgnoreCase(name))
          name = parseIdentifier();

        if (! "join".equalsIgnoreCase(name))
          throw error(L.l("expected JOIN at '{0}'", name));

        isRight = true;
        isOuter = true;

        throw error(L.l("right outer joins are not supported"));
      }
      else if ("natural".equalsIgnoreCase(_lexeme)) {
        String name = parseIdentifier();

        isNatural = true;

        if ("left".equalsIgnoreCase(name)) {
          name = parseIdentifier();

          if ("outer".equalsIgnoreCase(name))
            name = parseIdentifier();

          isOuter = true;
        }
        else if ("right".equalsIgnoreCase(name)) {
          name = parseIdentifier();

          if ("outer".equalsIgnoreCase(name))
            name = parseIdentifier();

          isRight = true;
          isOuter = true;

          throw error(L.l("right outer joins are not supported"));
        }

        if (! "join".equalsIgnoreCase(name))
          throw error(L.l("expected JOIN at '{0}'", name));
      }
      else {
        _token = token;
        break;
      }

      fromItem = parseFromItem();
      fromItems.add(fromItem);

      _query.setFromItems(fromItems);

      token = scanToken();
      if (token == IDENTIFIER && "on".equalsIgnoreCase(_lexeme)) {
        Expr onExpr = parseExpr();

        if (isOuter) {
          FromItem leftItem = fromItems.get(fromItems.size() - 2);
          FromItem rightItem = fromItems.get(fromItems.size() - 1);

          onExpr = new LeftOuterJoinExpr(rightItem, onExpr);

          rightItem.setDependTable(leftItem);
        }

        _andExpr.add(onExpr);
      }
      else
        _token = token;
    }

    return fromItems;
  }
  */

  /**
   * Parses a select expression.
   */
  private ExprKraken parseSelectExpr()
  {
    Token token = scanToken();

    if (token == Token.STAR) {
      // return new UnboundStarExpr();
      
      throw new UnsupportedOperationException(getClass().getName());
    }
    else {
      _token = token;

      return parseExpr();
    }
  }

  /**
   * Parses a from item
   */

  private String parseFrom()
  {
    return parseTableName();
    
    // String tableName = parseIdentifier();
    
    // return tableName;
    /*
    if (tableName.equalsIgnoreCase("DUAL"))
      return null;
      */

    /*
    TableKelp table = _db.getTable(tableName);

    if (table == null) {
      throw error("'{0}' is an unknown table.  'FROM table' requires an existing table.", tableName);
    }
    */

    /*
    String name = table.getName();

    Token token = scanToken();
    if (token == Token.AS)
      name = parseIdentifier();
    else if (token == IDENTIFIER)
      name = _lexeme;
    else
      _token = token;

    return new FromItem(table, name);
    */
    
    // return table;
  }

  /**
   * Parses the ORDER BY
   */
  /*
  private Order parseOrder(SelectQueryBuilder query,
                           ArrayList resultList)
    throws SQLException
  {
    int token;

    Order order = null;

    do {
      Expr expr = parseExpr();

      expr = expr.bind(query);

      token = scanToken();
      boolean isAsc = true;
      if (token == ASC)
        isAsc = true;
      else if (token == DESC)
        isAsc = false;
      else
        _token = token;

      int index;
      for (index = 0; index < resultList.size(); index++) {
        Expr resultExpr = resultList.get(index);

        if (expr.equals(resultExpr))
          break;
      }

      if (resultList.size() <= index) {
        resultList.add(expr);
      }

      Order tailOrder = expr.createOrder(index);
      tailOrder.setAscending(isAsc);

      order = Order.append(order, tailOrder);

      // ascList.add(isAsc ? Boolean.TRUE : Boolean.FALSE);
    } while ((token = scanToken()) == ',');

    query.setOrder(order);

    _token = token;

    return order;
  }
  */

  /**
   * Parses the GROUP BY
   */
  /*
  private ArrayList parseGroup(SelectQueryBuilder query)
    throws SQLException
  {
    query.setGroup(true);
    int token;

    ArrayList groupList = new ArrayList();

    do {
      groupList.add(parseExpr());
    } while ((token = scanToken()) == ',');

    _token = token;

    return groupList;
  }
  */

  /**
   * Parses the GROUP BY
   */
  /*
  private void bindGroup(SelectQueryBuilder query, 
                         ArrayList groupList)
    throws SQLException
  {
    query.setGroup(true);

    Expr []resultList = query.getResults();

    for (int i = 0; i < groupList.size(); i++) {
      Expr expr = groupList.get(i);

      expr = expr.bind(query);

      int index;
      for (index = 0; index < resultList.length; index++) {
        Expr resultExpr = resultList[index];

        if (expr.equals(resultExpr)) {
          resultList[index] = new GroupResultExpr(index, resultExpr);

          break;
        }
      }

      if (resultList.length <= index) {
        throw error(L.l("GROUP BY field '{0}' must refer to a result field.",
                        expr));
      }

      query.setGroupResult(index);
    }
  }
  */

  /**
   * Parses the LIMIT
   */
  /*
  private void parseLimit(SelectQueryBuilder query)
    throws SQLException
  {
    int token = scanToken();

    if (token == INTEGER) {
      query.setLimit(Integer.valueOf(_lexeme));
      _token = scanToken();
    }
    else
      throw error(L.l("LIMIT expected LIMIT int"));
  }
  */

  /**
   * Parses the create.
   */
  private QueryBuilderKraken parseCreate()
  {
    Token token;

    // TableBuilderKraken factory = null;// = _database.createTableFactory();

    if ((token = scanToken()) != Token.TABLE)
      throw error("expected TABLE at '{0}'", token);

    if ((token = scanToken()) != Token.IDENTIFIER)
      throw error("expected identifier at '{0}'", token);
    
    String name = _lexeme;
    String pod = null;
    
    while (peekToken() == Token.DOT) {
      scanToken();
      
      if ((token = scanToken()) != Token.IDENTIFIER) {
        throw error("expected identifier at '{0}'", token);
      }
    
      if (pod == null) {
        pod = name;
      }
      else {
        pod = pod + '.' + name;
      }
      
      name = _lexeme;
    }
    
    if (pod == null) {
      pod = getPodName();
    }

    TableBuilderKraken factory = new TableBuilderKraken(pod, name, _sql);
    // factory.startTable(_lexeme);

    if ((token = scanToken()) != Token.LPAREN) {
      throw error("expected '(' at '{0}'", token);
    }

    do {
      token = scanToken();

      switch (token) {
      case IDENTIFIER:
        parseCreateColumn(factory, _lexeme);
        break;

        /*
      case UNIQUE:
        token = scanToken();
        
        if (token != KEY) {
          _token = token;
        }
        
        factory.addUnique(parseColumnNames());
        break;
        */

      case PRIMARY:
        token = scanToken();
        if (token != Token.KEY)
          throw error("expected 'key' at {0}", token);

        factory.addPrimaryKey(parseColumnNames());
        break;

      case KEY:
        //String key = parseIdentifier();

        factory.addPrimaryKey(parseColumnNames()); // factory.addPrimaryKey(parseColumnNames());
        break;

        /*
      case CHECK:
        if ((token = scanToken()) != '(')
          throw error(L.l("Expected '(' at '{0}'", tokenName(token)));

        parseExpr();

        if ((token = scanToken()) != ')')
          throw error(L.l("Expected ')' at '{0}'", tokenName(token)));
        break;
        */

      default:
        throw error("unexpected token '{0}'", token);
      }

      token = scanToken();
    } while (token == Token.COMMA);

    if (token != Token.RPAREN) {
      throw error("expected ')' at '{0}'", token);
    }
    
    token = scanToken();
    
    HashMap propMap = new HashMap<>();
    
    if (token == Token.WITH) {
      do {
        String key = parseIdentifier();
        ExprKraken expr = parseExpr();

        if (! (expr instanceof LiteralExpr)) {
          throw error("WITH expression must be a literal at '{0}'", expr);
        }
        
        String value = expr.evalString(null);
        
        propMap.put(key, value);
      } while ((token = scanToken()) == Token.COMMA);
    }
    
    if (token != Token.EOF) {
      throw error("Expected end of file at '{0}'", token);
    }

    return new CreateQueryBuilder(_tableManager, factory, _sql, propMap);
  }
  
  public String getPodName()
  {
    if (_podName != null) {
      return _podName;
    }
    else {
      return getCurrentPodName();
    }
  }
  
  public static String getCurrentPodName()
  {
    String podName = BartenderSystem.getCurrentPod().name();
    String clusterId = BartenderSystem.getCurrentSelfServer().getClusterId();
    
    podName = podName.replace('-', '_');
    podName = podName.replace('.', '_');
    
    if (podName.equals(clusterId)) {
      podName = podName + "_hub";
    }
    
    return podName;
  }
  
  public static String getPodName(String tableName)
  {
    int p = tableName.indexOf('.');
    
    if (p > 0) {
      return tableName.substring(0, p);
    }
    
    return getCurrentPodName();
  }

  /**
   * Parses a column declaration.
   */
  private void parseCreateColumn(TableBuilderKraken factory, String name)
  {
    Token token;

    if ((token = scanToken()) != Token.IDENTIFIER) {
      throw error(L.l("expected column type at {0}", token));
    }

    String type = _lexeme;
    int length = -1;
    int scale = -1;

    if (type.equalsIgnoreCase("double")) {
      if ((token = peekToken()) == Token.IDENTIFIER) {
        scanToken();
        
        if (_lexeme.equalsIgnoreCase("precision")) {
        }
        else
          throw error(L.l("unexpected double type at {0}", _lexeme));
      }
    }

    if ((token = peekToken()) == Token.LPAREN) {
      scanToken();
      
      if ((token = scanToken()) != Token.INTEGER) {
        throw error("expected column width at '{0}'", token);
      }

      length = Integer.parseInt(_lexeme);

      if ((token = scanToken()) == Token.COMMA) {
        if ((token = scanToken()) != Token.INTEGER) {
          throw error("expected column scale at '{0}'", token);
        }

        scale = Integer.parseInt(_lexeme);

        token = scanToken();
      }

      if (token != Token.RPAREN) {
        throw error("expected ')' at '{0}'", token);
      }
    }

    if (type.equalsIgnoreCase("varchar")) {
      /*
      if (length < 0) {
        throw error(L.l("VARCHAR needs a defined length"));
      }
      */

      // factory.addVarchar(name, length);
      factory.addVarchar(name, length);
    }
    else if (type.equalsIgnoreCase("blob")) {
      factory.addBlob(name);
    }
    else if (type.equalsIgnoreCase("string")) {
      factory.addString(name);
    }
    else if (type.equalsIgnoreCase("object")) {
      factory.addObject(name);
    }
    else if (type.equalsIgnoreCase("char")) {
      length = Math.max(length, 1);

      factory.addVarchar(name, length);
    }
    else if (type.equalsIgnoreCase("varbinary")) {
      if (length < 0) {
        throw error("VARBINARY needs a defined length");
      }

      factory.addVarbinary(name, length);
    }
    else if (type.equalsIgnoreCase("binary")
        || type.equalsIgnoreCase("bytes")) {
      if (length < 0)
        throw error(L.l("BINARY needs a defined length"));

      factory.addBytes(name, length);
    }
    /*
    else if (type.equalsIgnoreCase("tinytext")) {
      factory.addTinytext(name);
    }
    */
    else if (type.equalsIgnoreCase("mediumtext")) {
      factory.addVarchar(name, 256);
    }
    else if (type.equalsIgnoreCase("longtext")) {
      factory.addVarchar(name, 512);
    }
    else if (type.equalsIgnoreCase("smallint")
             || type.equalsIgnoreCase("tinyint")
             || type.equalsIgnoreCase("bit")
             || type.equalsIgnoreCase("int16")) {
      factory.addInt16(name);
    }
    else if (type.equalsIgnoreCase("integer")
             || type.equalsIgnoreCase("int")
             || type.equalsIgnoreCase("mediumint")
             || type.equalsIgnoreCase("int32")) {
      factory.addInt32(name);
    }
    else if (type.equalsIgnoreCase("bigint")
             || type.equalsIgnoreCase("long")
             || type.equalsIgnoreCase("int64")) {
      factory.addInt64(name);
    }
    else if (type.equalsIgnoreCase("double")
             || type.equalsIgnoreCase("float")
             || type.equalsIgnoreCase("real")) {
      factory.addDouble(name);
    }
    else if (type.equalsIgnoreCase("datetime")
             || type.equalsIgnoreCase("timestamp")) {
      factory.addDateTime(name);
    }
    else if (type.equalsIgnoreCase("text")
             || type.equalsIgnoreCase("clob")) {
      factory.addVarchar(name, 255);
    }
    else if (type.equalsIgnoreCase("identity")) {
      factory.addIdentity(name);
    }
    else
      throw error(L.l("Unknown type {0}", type));

    /*
    token = scanToken();
    if (token == IDENTIFIER && _lexeme.equalsIgnoreCase("default")) {
      Expr defaultExpr = parseExpr();

      factory.setDefault(name, defaultExpr);
    }
    else {
      _token = token;
    }
    */

    while (true) {
      token = scanToken();

      // XXX: stuff like NOT NULL

      switch (token) {
      case RPAREN:
      case COMMA:
        _token = token;
        return;

        /*
      case UNIQUE:
        factory.setUnique(name);
        break;
        */

      case PRIMARY:
        token = scanToken();
        if (token != Token.KEY)
          throw error("expected key at {0}", token);

        factory.setPrimaryKey(name);
        break;

        /*
      case CHECK:
        if ((token = scanToken()) != '(')
          throw error(L.l("Expected '(' at '{0}'", tokenName(token)));

        parseExpr();

        if ((token = scanToken()) != ')')
          throw error(L.l("Expected ')' at '{0}'", tokenName(token)));
        break;
        */

        /*
      case IDENTIFIER:
        String id = _lexeme;
        if (id.equalsIgnoreCase("references")) {
          ArrayList foreignKey = new ArrayList();
          foreignKey.add(name);
          parseReferences(foreignKey);
        }
        else if (id.equalsIgnoreCase("default")) {
          Expr expr = parseExpr();
        }
        else if (id.equalsIgnoreCase("auto_increment")) {
          factory.setAutoIncrement(name, 1);
        }
        else if (id.equalsIgnoreCase("unsigned")) {
        }
        else if (id.equalsIgnoreCase("binary")) {
        }
        else
          throw error(L.l("unexpected token '{0}'", tokenName(token)));
        break;
        */

      case NULL:
        break;

      case NOT:
        if ((token = scanToken()) == Token.NULL)
          factory.setNotNull(name);
        else
          throw error("unexpected token '{0}'", token);
        break;

      default:
        throw error("unexpected token '{0}'", token);
      }
    }
  }
  
  /**
   * Parses a key constraint declaration.
   */
  /*
  private void parseKeyConstraint(TableFactoryKelp factory)
  {
    String key = parseIdentifier();

    Token token = scanToken();

    if (token == Token.LPAREN) {
      parseIdentifier();

      token = scanToken();
      if (token != Token.RPAREN) {
        throw error("expected ')' at {0}", token);
      }
    }
    else {
      _token = token;
    }
  }
  */

  /**
   * Parses the references clause.
   */
  public void parseReferences(ArrayList name)
  {
    String foreignTable = parseIdentifier();

    Token token = scanToken();

    ArrayList foreignColumns = new ArrayList();

    if (token == Token.LPAREN) {
      _token = token;

      foreignColumns = parseColumnNames();
    }
    else {
      _token = token;
    }
  }

  /**
   * Parses a list of column names
   */
  public ArrayList parseColumnNames()
  {
    ArrayList columns = new ArrayList();

    Token token = scanToken();
    if (token == Token.LPAREN) {
      do {
        columns.add(parseIdentifier());

        token = scanToken();
      } while (token == Token.COMMA);

      if (token != Token.RPAREN) {
        throw error("expected ')' at '{0}'", token);
      }
    }
    else if (token == Token.IDENTIFIER) {
      columns.add(_lexeme);

      _token = token;
    }
    else {
      throw error("expected '(' at '{0}'", token);
    }

    return columns;
  }

  /**
   * Parses the insert.
   */
  private QueryBuilderKraken parseInsert()
  {
    Token token;

    if ((token = scanToken()) != Token.INTO) {
      throw error("expected INTO at '{0}'", token);
    }

    TableKraken table = parseTable();

    Objects.requireNonNull(table);
    
    TableKelp tableKelp = table.getTableKelp();

    ArrayList columns = new ArrayList<>();
    
    boolean isKeyHash = false;

    if ((token = scanToken()) == Token.LPAREN) {
      do {
        String columnName = parseIdentifier();

        Column column = tableKelp.getColumn(columnName);

        if (column == null) {
          throw error("'{0}' is not a valid column in {1}",
                      columnName, table.getName());
        }
        
        columns.add(column);
      } while ((token = scanToken()) == Token.COMMA);

      if (token != Token.RPAREN) {
        throw error("expected ')' at '{0}'", token);
      }

      token = scanToken();
    }
    else {
      for (Column column : tableKelp.getColumns()) {
        if (column.name().startsWith(":")) {
          continue;
        }
        
        columns.add(column);
      }
    }

    if (token != Token.VALUES)
      throw error("expected VALUES at '{0}'", token);

    if ((token = scanToken()) != Token.LPAREN) {
      throw error("expected '(' at '{0}'", token);
    }

    ArrayList values = new ArrayList<>();

    InsertQueryBuilder query;
    
    query = new InsertQueryBuilder(this, _sql, table, columns);
    _query = query;

    do {
      ExprKraken expr = parseExpr();

      //expr = expr.bind(new TempQueryBuilder(table));

      values.add(expr);
    } while ((token = scanToken()) == Token.COMMA);

    if (token != Token.RPAREN) {
      throw error("expected ')' at '{0}'", token);
    }

    if (columns.size() != values.size()) {
      throw error("number of columns does not match number of values");
    }

    ParamExpr []params = _params.toArray(new ParamExpr[_params.size()]);

    query.setParams(params);
    query.setValues(values);
    // query.init();

    return query;
  }

  /**
   * Parses replace.
   */
  private QueryBuilderKraken parseReplace()
  {
    Token token;

    TableKraken table = parseTable();

    Objects.requireNonNull(table);
    
    TableKelp tableKelp = table.getTableKelp();

    ArrayList columns = new ArrayList<>();
    
    if ((token = scanToken()) == Token.LPAREN) {
      do {
        String columnName = parseIdentifier();

        Column column = tableKelp.getColumn(columnName);

        if (column == null) {
          throw error("'{0}' is not a valid column in {1}",
                      columnName, table.getName());
        }
        
        columns.add(column);
      } while ((token = scanToken()) == Token.COMMA);

      if (token != Token.RPAREN) {
        throw error("expected ')' at '{0}'", token);
      }

      token = scanToken();
    }
    else {
      for (Column column : tableKelp.getColumns()) {
        if (column.name().startsWith(":")) {
          continue;
        }
        
        columns.add(column);
      }
    }

    if (token != Token.VALUES)
      throw error("expected VALUES at '{0}'", token);

    if ((token = scanToken()) != Token.LPAREN) {
      throw error("expected '(' at '{0}'", token);
    }

    ArrayList values = new ArrayList<>();

    ReplaceQueryBuilder query;
    
    query = new ReplaceQueryBuilder(_tableManager, _sql, table, columns);
    _query = query;

    do {
      ExprKraken expr = parseExpr();

      //expr = expr.bind(new TempQueryBuilder(table));

      values.add(expr);
    } while ((token = scanToken()) == Token.COMMA);

    if (token != Token.RPAREN) {
      throw error("expected ')' at '{0}'", token);
    }

    if (columns.size() != values.size()) {
      throw error("number of columns does not match number of values");
    }

    ParamExpr []params = _params.toArray(new ParamExpr[_params.size()]);

    query.setParams(params);
    query.setValues(values);
    // query.init();

    return query;
  }

  private TableKraken parseTable()
  {
    String tableName = parseTableName();
    
    TableKraken table = _tableManager.loadTable(tableName);
    
    if (table == null) {
      throw error("unknown table '{0}'", tableName);
    }
  
    return table;
  }
  
  private String parseTableName()
  {
    Token token;
    
    String tableName = null;
    
    while (true) {
      if ((token = scanToken()) != Token.IDENTIFIER) {
        throw error("expected identifier at '{0}'", token);
      }
      
      if (tableName == null) {
        tableName = _lexeme;
      }
      else {
        tableName = tableName + '.' + _lexeme;
      }
      
      if (peekToken() != Token.DOT) {
        return fullTableName(tableName);
      }
      
      scanToken();
    }
  }
  
  private String fullTableName(String tableName)
  {
    if (tableName.indexOf('.') >= 0) {
      return tableName;
    }
    else {
      return getPodName() + '.' + tableName;
    }
  }

  /**
   * Parses the delete.
   */
  private QueryBuilderKraken parseDelete()
  {
    DeleteQueryBuilder query
    = new DeleteQueryBuilder(_tableManager, _sql);
    
    Token token;

    if ((token = scanToken()) != Token.FROM) {
      throw error("expected FROM at '{0}'", token);
    }
    
    String tableName = parseTableName();

    query.setTableName(tableName);

    /*
    Table table = _database.getTable(_lexeme);

    if (table == null)
      throw error("unknown table '{0}'", token);

    DeleteQueryBuilder query = new DeleteQueryBuilder(_database, _sql, table);
    */
    
    _query = query;

    ExprKraken whereExpr = null;

    token = scanToken();
    
    if (token == Token.WHERE) { 
      whereExpr = parseExpr();
    }
    else if (token != null) {
      throw error("expected WHERE at '{0}'", token);
    }

    ParamExpr []params = _params.toArray(new ParamExpr[_params.size()]);

    query.setParams(params);
    query.setWhereExpr(whereExpr);

    return query;
  }

  /**
   * watch: adding and removing listeners.
   */
  private QueryBuilderKraken parseWatch()
  {
    WatchQueryBuilder query
      = new WatchQueryBuilder(_tableManager, _sql);
    
    Token token;
    
    String tableName = parseTableName();

    query.setTableName(tableName);
    
    _query = query;

    ExprKraken whereExpr = null;

    token = scanToken();
    
    if (token == Token.WHERE) {
      whereExpr = parseExpr();
    }
    else if (token != null) {
      throw error("expected WHERE at '{0}'", token);
    }

    ParamExpr []params = _params.toArray(new ParamExpr[_params.size()]);

    query.setParams(params);
    query.setWhereExpr(whereExpr);

    return query;
  }

  /**
   * notify: signals to watch without updating data
   */
  private QueryBuilderKraken parseNotify()
  {
    NotifyQueryBuilder query
      = new NotifyQueryBuilder(_tableManager, _sql);
    
    Token token;
    
    String tableName = parseTableName();

    query.setTableName(tableName);
    
    _query = query;

    ExprKraken whereExpr = null;

    token = scanToken();
    
    if (token == Token.WHERE) {
      whereExpr = parseExpr();
    }
    else if (token != null) {
      throw error("expected WHERE at '{0}'", token);
    }

    ParamExpr []params = _params.toArray(new ParamExpr[_params.size()]);

    query.setParams(params);
    query.setWhereExpr(whereExpr);

    return query;
  }

  /**
   * Parses the delete.
   */
  /*
  private QueryBuilder parseValidate()
  {
    int token;

    if ((token = scanToken()) != IDENTIFIER)
      throw error(L.l("expected identifier at '{0}'", tokenName(token)));

    Table table = _database.getTable(_lexeme);

    if (table == null)
      throw error(L.l("unknown table '{0}'", tokenName(token)));

    ValidateQueryBuilder query
      = new ValidateQueryBuilder(_database, _sql, table);

    return query;
  }
  */

  /**
   * Parses the insert.
   */
  /*
  private QueryBuilder parseDrop()
  {
    int token;

    if ((token = scanToken()) != TABLE)
      throw error(L.l("expected TABLE at `{0}'", tokenName(token)));

    if ((token = scanToken()) != IDENTIFIER)
      throw error(L.l("expected identifier at `{0}'", tokenName(token)));

    String table = _lexeme;

    if ((token = scanToken()) >= 0)
      throw error(L.l("expected end of query at `{0}'", tokenName(token)));

    return new DropQueryBuilder(_sql, _database, table);
  }
  */

  /**
   * Parses the update.
   * 
   * UPDATE table_name SET a=?,b=? WHERE expr
   */
  private QueryBuilderKraken parseUpdate()
  {
    Token token;
    
    UpdateQueryBuilder query = new UpdateQueryBuilder(_tableManager, _sql);
    
    String tableName = parseTableName();

    query.setTableName(tableName);

    _query = query;

    if ((token = scanToken()) != Token.SET) {
      throw error(L.l("expected SET at {0}", token));
    }

    do {
      parseSetItem(query);
    } while ((token = scanToken()) == Token.COMMA);
    
    _token = token;

    //query.setUpdateBuilder(new SetUpdateBuilder(setItems));
    
    ExprKraken whereExpr = null;

    token = scanToken();
    
    if (token == Token.WHERE) {
      whereExpr = parseExpr();
    }
    else if (token != null && token != Token.EOF) {
      throw error("expected WHERE at '{0}'", token);
    }

    ParamExpr []params = _params.toArray(new ParamExpr[_params.size()]);

    query.setParams(params);
    query.setWhereExpr(whereExpr);

    return query;
  }
  
  /*
  private Query parseShow()
  {
    int token;

    if ((token = scanToken()) != IDENTIFIER)
      throw error(L.l("expected identifier at `{0}'", tokenName(token)));

    String name = _lexeme;
    
    if (name.equalsIgnoreCase("tables"))
      return new ShowTablesQuery();
    else if (name.equalsIgnoreCase("databases"))
      return new ShowDatabasesQuery();
    else
      throw error(L.l("`{0}' is an unknown type in SHOW.", name));
  }
  */

  /**
   * Parses a set item.
   */
  private void parseSetItem(UpdateQueryBuilder query)
  {
    Token token;

    if ((token = scanToken()) != Token.IDENTIFIER) {
      throw error(L.l("expected identifier at '{0}'", token));
    }

    String columnName = _lexeme;

    if ((token = scanToken()) != Token.EQ) {
      throw error("expected '=' at {0}", token);
    }

    ExprKraken expr = parseExpr();

    query.addItem(columnName, expr);
  }

  /**
   * Parses an expression.
   */

  private ExprKraken parseExpr()
  {
    return parseOrExpr();
  }

  /**
   * Parses a +/- expression.
   */
  private ExprKraken parseOrExpr()
  {
    ExprKraken left = parseAndExpr();

    while (true) {
      Token token = scanToken();

      switch (token) {
      case OR:
        left = new BinaryExpr(BinaryOp.OR, left, parseAndExpr());
        break;

      default:
        _token = token;
        return left;
      }
    }
  }

  /**
   * Parses an AND expression.
   */
  private ExprKraken parseAndExpr()
  {
    // AndExpr oldAndExpr = _andExpr;
    AndExpr andExpr = new AndExpr();
    // _andExpr = andExpr;

    andExpr.add(parseNotExpr());

    while (true) {
      Token token = scanToken();

      switch (token) {
      case AND:
        andExpr.add(parseNotExpr());
        break;

      default:
        _token = token;

        // _andExpr = oldAndExpr;

        return andExpr.getSingleExpr();
      }
    }
  }

  private ExprKraken parseNotExpr()
  {
    return parseCmpExpr();
  }

  /**
   * Parses a term.
   */
  /*
  private Expr parseNotExpr()
  {
    int token = scanToken();

    switch (token) {
    case NOT:
      return new NotExpr(parseNotExpr());

    default:
      _token = token;
      return parseCmpExpr();
    }
  }
  */

  /**
   * Parses a CMP expression.
   */

  private ExprKraken parseCmpExpr()
  {
    // Expr left = parseConcatExpr();
    ExprKraken left = parseAddExpr();

    Token token = scanToken();
    
    boolean isNot = false;
    
    /*

    if (token == NOT) {
      isNot = true;

      token = scanToken();

      if (token != BETWEEN && token != LIKE && token != IN) {
        _token = token;

        return left;
      }
    }
    */

    switch (token) {
    case EQ:
      return new BinaryExpr(BinaryOp.EQ, left, parseAddExpr());
      
    case NE:
      return new BinaryExpr(BinaryOp.NE, left, parseAddExpr());
      
    case LT:
      return new BinaryExpr(BinaryOp.LT, left, parseAddExpr());
      
    case LE:
      return new BinaryExpr(BinaryOp.LE, left, parseAddExpr());
      
    case GT:
      return new BinaryExpr(BinaryOp.GT, left, parseAddExpr());
      
    case GE:
      return new BinaryExpr(BinaryOp.GE, left, parseAddExpr());

    case BETWEEN:
      {
        ExprKraken min = parseAddExpr();

        token = scanToken();
        if (token != Token.AND)
          throw error(L.l("expected AND at '{0}'", token));

        ExprKraken max = parseAddExpr();

        return new BetweenExpr(left, min, max, isNot);
      }
/*
    case LT:
    case LE:
    case GT:
    case GE:
    case NE:
      return new CmpExpr(left, parseConcatExpr(), token);

    case BETWEEN:
      {
        Expr min = parseConcatExpr();

        token = scanToken();
        if (token != AND)
          throw error(L.l("expected AND at '{0}'", tokenName(token)));

        Expr max = parseConcatExpr();

        return new BetweenExpr(left, min, max, isNot);
      }

    case IS:
      {
        token = scanToken();
        isNot = false;
        if (token == NOT) {
          token = scanToken();
          isNot = true;
        }

        if (token == NULL)
          return new IsNullExpr(left, isNot);
        else
          throw error(L.l("expected NULL at '{0}'", tokenName(token)));
      }

    case LIKE:
      {
        token = scanToken();

        if (token == STRING)
          return new LikeExpr(left, _lexeme, isNot);
        else
          throw error(L.l("expected string at '{0}'", tokenName(token)));
      }

    case IN:
      {
        HashSet values = parseInValues();

        return new InExpr(left, values, isNot);
      }
*/
    default:
      _token = token;
      return left;
    }
  }

  /**
   * Parses the IN values.
   */
  /*
  private HashSet parseInValues()
  {
    int token = scanToken();

    if (token != '(')
      throw error(L.l("Expected '('"));

    HashSet values = new HashSet();

    while ((token = scanToken()) != ')') {
      if (token == STRING) {
        values.add(_lexeme);
      }
      else
        throw error(L.l("expected STRING at {0}", tokenName(token)));

      if ((token = scanToken()) != ',')
        break;
    }

    if (token != ')')
        throw error(L.l("expected ')' at {0}", tokenName(token)));

    return values;
  }
  */

  /**
   * Parses a concat expression.
   */
  /*
  private Expr parseConcatExpr()
  {
    Expr left = parseAddExpr();

    while (true) {
      int token = scanToken();

      switch (token) {
      case CONCAT:
        left = new ConcatExpr(left, parseAddExpr());
        break;

      default:
        _token = token;
        return left;
      }
    }
  }
  */

  /**
   * Parses a +/- expression.
   */
  private ExprKraken parseAddExpr()
  {
    ExprKraken left = parseMulExpr();

    while (true) {
      Token token = scanToken();

      switch (token) {
      case PLUS:
        left = new BinaryExpr(BinaryOp.ADD, left, parseSimpleTerm());
        break;
      case MINUS:
        left = new BinaryExpr(BinaryOp.SUB, left, parseSimpleTerm());
        break;

      default:
        _token = token;
        return left;
      }
    }
  }

  /**
   * Parses a mul/div expression
   */
  private ExprKraken parseMulExpr()
  {
    ExprKraken left = parseSimpleTerm();

    while (true) {
      Token token = scanToken();

      switch (token) {
      case STAR:
        left = new BinaryExpr(BinaryOp.MUL, left, parseSimpleTerm());
        break;
      case DIV:
        left = new BinaryExpr(BinaryOp.DIV, left, parseSimpleTerm());
        break;
      case MOD:
        left = new BinaryExpr(BinaryOp.MOD, left, parseSimpleTerm());
        break;

      default:
        _token = token;
        return left;
      }
    }
  }

  /**
   * Parses a term.
   */
  /*
  private Expr parseTerm()
  {
    int token = scanToken();

    switch (token) {
    case '+':
      return parseTerm();

    case '-':
      return new UnaryExpr(parseTerm(), token);

    case '(':
      Expr expr = parseExpr();
      int peekToken;
      if ((peekToken = scanToken()) != ')')
        throw error(L.l("expected ')' at {0}", tokenName(peekToken)));
      return expr;

    default:
      _token = token;
      return parseSimpleTerm();
    }
  }
  */

  /**
   * Parses a simple term.
   */

  private ExprKraken parseSimpleTerm()
  {
    Token token = scanToken();

    switch (token) {
    case MINUS:
      return new UnaryExpr(UnaryOp.MINUS, parseSimpleTerm());
      
    case LPAREN:
    {
      ExprKraken expr = parseExpr();
      
      if ((token = scanToken()) != Token.RPAREN) {
        throw error("Expected ')' at '{0}'", token);
      }
      
      return expr;
    }
      
      
    case IDENTIFIER:
      {
        String name = _lexeme;

        if ((token = peekToken()) == Token.DOT) {
          return parsePath(name);
        }
        else if (token == Token.LPAREN) {
          FunExpr fun = null;
          
          /*
          if (name.equalsIgnoreCase("max"))
            fun = new MaxExpr();
          else if (name.equalsIgnoreCase("min"))
            fun = new MinExpr();
          else if (name.equalsIgnoreCase("sum"))
            fun = new SumExpr();
          else if (name.equalsIgnoreCase("avg"))
            fun = new AvgExpr();
          else if (name.equalsIgnoreCase("count")) {
            fun = new CountExpr();

            token = scanToken();
            if (token == '*') {
              fun.addArg(new UnboundStarExpr());
            }
            else
              _token = token;
          }
          else */
          {
            String funName = (Character.toUpperCase(name.charAt(0)) +
                              name.substring(1).toLowerCase(Locale.ENGLISH));

            funName = "com.caucho.v5.kraken.fun." + funName + "Expr";

            try {
              ClassLoader loader = Thread.currentThread().getContextClassLoader();
              Class cl = Class.forName(funName, false, loader);

              fun = (FunExpr) cl.newInstance();
            } catch (ClassNotFoundException e) {
              log.finer(e.toString());
            } catch (Exception e) {
              log.log(Level.FINER, e.toString(), e);
              
              throw error(e.toString());
            }

            if (fun == null) {
              throw error(L.l("'{0}' is an unknown function.", name));
            }
          }

          scanToken();
          token = peekToken();
          while (token != null && token != Token.RPAREN) {
            ExprKraken arg = parseExpr();

            fun.addArg(arg);

            token = peekToken();

            if (token == Token.COMMA) {
              scanToken();
              token = peekToken();
            }
          }

          scanToken();

          return fun;
        }
        else {
          return new IdExprBuilder(name);
        }
      }

    case STRING:
      return new LiteralExpr(_lexeme);

    case DOUBLE:
      return new LiteralExpr(Double.parseDouble(_lexeme));
      
    case INTEGER:
      return new LiteralExpr(Long.parseLong(_lexeme));
      
    case LONG:
      return new LiteralExpr(Long.parseLong(_lexeme));

    case NULL:
      return new NullExpr();

    case TRUE:
      return new LiteralExpr(true);

    case FALSE:
      return new LiteralExpr(false);

    case QUESTION_MARK:
      ParamExpr param = new ParamExpr(_params.size());
      _params.add(param);
      return param;

    default:
      throw error("unexpected term {0}", token);
    }
  }

  private ExprKraken parsePath(String name)
  {
    ExprKraken term = new IdExprBuilder(name);
    Token token;
    
    while (true) {
      switch (peekToken()) {
      case DOT: {
        scanToken();

        if ((token = scanToken()) != Token.IDENTIFIER) {
          throw error("expected IDENTIFIER at {0}", token);
        }
        
        
        term = term.field(_lexeme);
        break;
      }
      
      default:
        return term;
      }
    }
  }


  /**
   * Parses an identifier.
   */
  private String parseIdentifier()
  {
    Token token = scanToken();

    if (token != Token.IDENTIFIER) { 
      throw error("expected identifier at {0}", token);
    }

    return _lexeme;
  }
  
  private Token peekToken()
  {
    Token token = scanToken();
    
    _token = token;
    
    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 Token scanToken()
  {
    Token token = _token;
    
    if (token != null) {
      _token = null;
      
      return token;
    }

    int sign = 1;
    int ch;

    for (ch = read(); Character.isWhitespace((char) ch); ch = read()) {
    }

    switch (ch) {
    case -1:
      return Token.EOF;
      
    case '(':
      return Token.LPAREN;
    case ')':
      return Token.RPAREN;
    case ',':
      return Token.COMMA;
    case '*':
      return Token.STAR;
    case '-':
      return Token.MINUS;
    case '+':
      return Token.PLUS;
    case '/':
      return Token.DIV;
    case '%':
      return Token.MOD;
    case '.':
      return Token.DOT;
    case '?':
      return Token.QUESTION_MARK;
      /*
    case '/':
    case '%':
    case '?':
      return ch;
      */

      /*
    case '+':
      if ((ch = read()) >= '0' && ch <= '9')
        break;
      else {
        unread(ch);
        return '+';
      }

    case '-':
      if ((ch = read()) >= '0' && ch <= '9') {
        sign = -1;
        break;
      }
      else {
        unread(ch);
        return '-';
      }
      */

    case '=':
      return Token.EQ;

    case '<':
      if ((ch = read()) == '=')
        return Token.LE;
      else if (ch == '>')
        return Token.NE;
      else {
        unread(ch);
        return Token.LT;
      }

    case '>':
      if ((ch = read()) == '=')
        return Token.GE;
      else {
        unread(ch);
        return Token.GT;
      }

      /*
    case '|':
      if ((ch = read()) == '|')
        return CONCAT;
      else {
        throw error(L.l("'|' expected at {0}", charName(ch)));
      }

      // @@ is useless?
    case '@':
      if ((ch = read()) != '@')
        throw error(L.l("`@' expected at {0}", charName(ch)));
      return scanToken();
    */
    }

    if (Character.isJavaIdentifierStart((char) ch) || ch == ':') {
      CharBuffer cb = _cb;
      cb.clear();

      for (; ch > 0 && isIdentifierPart((char) ch); ch = read()) {
        cb.append((char) ch);
      }

      unread(ch);

      _lexeme = cb.toString();
      String lower = _lexeme.toLowerCase(Locale.ENGLISH);

      token = _reserved.get(lower);

      if (token != null)
        return token;
      else
        return Token.IDENTIFIER;
    }
    else if (ch >= '0' && ch <= '9') {
      CharBuffer cb = _cb;
      cb.clear();

      Token type = Token.INTEGER;

      if (sign < 0)
        cb.append('-');

      for (; ch >= '0' && ch <= '9'; ch = read())
        cb.append((char) ch);

      if (ch == '.') {
        type = Token.DOUBLE;

        cb.append('.');
        for (ch = read(); ch >= '0' && ch <= '9'; ch = read())
          cb.append((char) ch);
      }

      if (ch == 'e' || ch == 'E') {
        type = Token.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 = Token.DOUBLE;
      else if (ch == 'L') {
        type = Token.LONG;
      }
      else
        unread(ch);

      _lexeme = cb.toString();

      return type;
    }
    else if (ch == '\'') {
      CharBuffer cb = _cb;
      cb.clear();

      for (ch = read(); ch >= 0; ch = read()) {
        if (ch == '\'') {
          if ((ch = read()) == '\'')
            cb.append('\'');
          else {
            unread(ch);
            break;
          }
        }
        else if (ch == '\\') {
          ch = read();

          if (ch >= 0)
            cb.append(ch);
        }
        else
          cb.append((char) ch);
      }

      _lexeme = cb.toString();

      return Token.STRING;
    }
    else if (ch == '#') {
      // skip comment
      while ((ch = read()) >= 0 && ch != '\n' && ch != '\r') {
      }

      // XXX: cleanup to avoid recursion
      return scanToken();
    }

    throw error(L.l("unexpected char at {0} ({1})", "" + (char) ch,
                    String.valueOf(ch)));
  }
  
  private boolean isIdentifierPart(char ch)
  {
    return Character.isJavaIdentifierPart(ch) || ch == ':';
  }

  /**
   * Returns the next character.
   */
  private int read()
  {
    if (_parseIndex < _sqlLength)
      return _sqlChars[_parseIndex++];
    else
      return -1;
  }

  /**
   * Unread the last character.
   */
  private void unread(int ch)
  {
    if (ch >= 0)
      _parseIndex--;
  }

  /**
   * 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);
  }

  private RuntimeException error(String msg, Object ...args)
  {
    return new QueryException(L.l(msg, args) + "\n" + _sql);
  }
  
  enum Token {
    EOF("end of file"),
    
    COMMA(","),
    DOT("."),
    LPAREN("("),
    RPAREN(")"),
    QUESTION_MARK("?"),
    SEMICOLON(";"),
    
    PLUS("+"),
    MINUS("-"),
    STAR("*"),
    DIV("/"),
    MOD("%"),
    
    EQ("="),
    LT("<"),
    LE("<="),
    GT(">"),
    GE(">="),
    NE("<>"),
    
    IDENTIFIER,
    INTEGER,
    DOUBLE,
    LONG,
    STRING,
    
    AND,
    BETWEEN,
    CREATE,
    DELETE,
    DISTINCT,
    EXPLAIN,
    FALSE,
    FROM,
    INSERT,
    INTO,
    KEY,
    LIMIT,
    MAP,
    NOT,
    NOTIFY,
    NULL,
    OFFSET,
    OR,
    PRIMARY,
    
    REPLACE,
    
    SELECT,
    SELECT_LOCAL,
    SET,
    SHOW,
    
    TABLE,
    TRUE,
    
    UNWATCH,
    UPDATE,
    
    VALUES,
    
    WATCH,
    WHERE,
    WITH,
    ;
    
    private String _displayName;
    
    Token()
    {
      _displayName = name();
    }
    
    Token(String name)
    {
      _displayName = name;
    }
    
    public String toString()
    {
      return _displayName;
    }
  }

  static {
    _reserved = new HashMap<>();
    
    _reserved.put("and", Token.AND);
    
    _reserved.put("between", Token.BETWEEN);
    
    _reserved.put("create", Token.CREATE);
    
    _reserved.put("delete", Token.DELETE);
    _reserved.put("distinct", Token.DISTINCT);
    
    _reserved.put("eq", Token.EQ);
    _reserved.put("ne", Token.NE);
    _reserved.put("lt", Token.LT);
    _reserved.put("le", Token.LE);
    _reserved.put("gt", Token.GT);
    _reserved.put("ge", Token.GE);
    
    _reserved.put("explain", Token.EXPLAIN);
    
    _reserved.put("false", Token.FALSE);
    _reserved.put("from", Token.FROM);
    
    _reserved.put("insert", Token.INSERT);
    _reserved.put("into", Token.INTO);
    
    _reserved.put("key", Token.KEY);
    
    _reserved.put("limit", Token.LIMIT);
    
    _reserved.put("map", Token.MAP);
    _reserved.put("notify", Token.NOTIFY);
    
    _reserved.put("offset", Token.OFFSET);
    _reserved.put("or", Token.OR);
    
    _reserved.put("primary", Token.PRIMARY);
    
    _reserved.put("replace", Token.REPLACE);
    
    _reserved.put("select", Token.SELECT);
    _reserved.put("select_local", Token.SELECT_LOCAL);
    _reserved.put("set", Token.SET);
    _reserved.put("show", Token.SHOW);
    
    _reserved.put("table", Token.TABLE);
    _reserved.put("true", Token.TRUE);
    
    _reserved.put("unwatch", Token.UNWATCH);
    _reserved.put("update", Token.UPDATE);
    
    _reserved.put("values", Token.VALUES);
    
    _reserved.put("watch", Token.WATCH);
    _reserved.put("where", Token.WHERE);
    _reserved.put("with", Token.WITH);
    
    /*
    _reserved = new IntMap();
    _reserved.put("as", AS);
    _reserved.put("from", FROM);
    _reserved.put("in", IN);
    _reserved.put("select", SELECT);
    _reserved.put("distinct", DISTINCT);
    _reserved.put("where", WHERE);
    _reserved.put("order", ORDER);
    _reserved.put("group", GROUP);
    _reserved.put("by", BY);
    _reserved.put("asc", ASC);
    _reserved.put("desc", DESC);
    _reserved.put("limit", LIMIT);
    _reserved.put("offset", OFFSET);

    _reserved.put("or", OR);
    _reserved.put("and", AND);
    _reserved.put("not", NOT);

    _reserved.put("between", BETWEEN);
    _reserved.put("like", LIKE);
    _reserved.put("escape", ESCAPE);
    _reserved.put("is", IS);

    _reserved.put("true", TRUE);
    _reserved.put("false", FALSE);
    _reserved.put("unknown", UNKNOWN);
    _reserved.put("null", NULL);

    _reserved.put("create", CREATE);
    _reserved.put("table", TABLE);
    _reserved.put("insert", INSERT);
    _reserved.put("into", INTO);
    _reserved.put("values", VALUES);
    _reserved.put("drop", DROP);
    _reserved.put("update", UPDATE);
    _reserved.put("set", SET);
    _reserved.put("delete", DELETE);
    _reserved.put("validate", VALIDATE);

    _reserved.put("constraint", CONSTRAINT);
    _reserved.put("unique", UNIQUE);
    _reserved.put("check", CHECK);
    _reserved.put("primary", PRIMARY);
    _reserved.put("key", KEY);
    _reserved.put("foreign", FOREIGN);
    */
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy