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

studio.raptor.sqlparser.fast.command.FastParser Maven / Gradle / Ivy

/*
 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 *
 * Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
 * Support for the operator "&&" as an alias for SPATIAL_INTERSECTS
 */
package studio.raptor.sqlparser.fast.command;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashSet;
import studio.raptor.sqlparser.fast.api.ErrorCode;
import studio.raptor.sqlparser.fast.command.dml.Delete;
import studio.raptor.sqlparser.fast.command.dml.Insert;
import studio.raptor.sqlparser.fast.command.dml.NoOperation;
import studio.raptor.sqlparser.fast.command.dml.ScriptCommand;
import studio.raptor.sqlparser.fast.command.dml.Update;
import studio.raptor.sqlparser.fast.engine.Constants;
import studio.raptor.sqlparser.fast.expression.CompareLike;
import studio.raptor.sqlparser.fast.expression.Comparison;
import studio.raptor.sqlparser.fast.expression.ConditionAndOr;
import studio.raptor.sqlparser.fast.expression.ConditionNot;
import studio.raptor.sqlparser.fast.expression.Expression;
import studio.raptor.sqlparser.fast.expression.ExpressionColumn;
import studio.raptor.sqlparser.fast.expression.ExpressionList;
import studio.raptor.sqlparser.fast.expression.Operation;
import studio.raptor.sqlparser.fast.expression.Parameter;
import studio.raptor.sqlparser.fast.expression.SequenceValue;
import studio.raptor.sqlparser.fast.expression.ValueExpression;
import studio.raptor.sqlparser.fast.expression.Wildcard;
import studio.raptor.sqlparser.fast.table.Table;
import studio.raptor.sqlparser.fast.value.ValueLong;
import studio.raptor.sqlparser.fast.message.ParseException;
import studio.raptor.sqlparser.fast.table.Column;
import studio.raptor.sqlparser.fast.util.MathUtils;
import studio.raptor.sqlparser.fast.util.New;
import studio.raptor.sqlparser.fast.util.StatementBuilder;
import studio.raptor.sqlparser.fast.util.StringUtils;
import studio.raptor.sqlparser.fast.value.DataType;
import studio.raptor.sqlparser.fast.value.Value;
import studio.raptor.sqlparser.fast.value.ValueBoolean;
import studio.raptor.sqlparser.fast.value.ValueBytes;
import studio.raptor.sqlparser.fast.value.ValueDate;
import studio.raptor.sqlparser.fast.value.ValueDecimal;
import studio.raptor.sqlparser.fast.value.ValueInt;
import studio.raptor.sqlparser.fast.value.ValueString;
import studio.raptor.sqlparser.fast.value.ValueTime;
import studio.raptor.sqlparser.fast.value.ValueTimestamp;


/**
 * The parser is used to convert a SQL statement string to an command object.
 */
public class FastParser {

  // used during the tokenizer phase
  private static final int CHAR_END = 1, CHAR_VALUE = 2, CHAR_QUOTED = 3;
  private static final int CHAR_NAME = 4, CHAR_SPECIAL_1 = 5, CHAR_SPECIAL_2 = 6;
  private static final int CHAR_STRING = 7, CHAR_DOT = 8, CHAR_DOLLAR_QUOTED_STRING = 9;

  // this are token types
  private static final int KEYWORD = 1, IDENTIFIER = 2, PARAMETER = 3,
      END = 4, VALUE = 5;
  private static final int EQUAL = 6, BIGGER_EQUAL = 7, BIGGER = 8;
  private static final int SMALLER = 9, SMALLER_EQUAL = 10, NOT_EQUAL = 11,
      AT = 12;
  private static final int MINUS = 13, PLUS = 14, STRING_CONCAT = 15;
  private static final int OPEN = 16, CLOSE = 17, NULL = 18, TRUE = 19,
      FALSE = 20;
  private static final int CURRENT_TIMESTAMP = 21, CURRENT_DATE = 22,
      CURRENT_TIME = 23, ROWNUM = 24;
  private static final int SPATIAL_INTERSECTS = 25;

  private final boolean identifiersToUpper = true;

  /**
   * indicates character-type for each char in sqlCommand
   */
  private int[] characterTypes;
  private int currentTokenType;
  private String currentToken;
  private boolean currentTokenQuoted;
  private Value currentValue;
  private String currentSchema;
  private String originalSQL;
  /**
   * copy of originalSQL, with comments blanked out
   */
  private String sqlCommand;
  /**
   * cached array if chars from sqlCommand
   */
  private char[] sqlCommandChars;
  /**
   * index into sqlCommand of previous token
   */
  private int lastParseIndex;
  /**
   * index into sqlCommand of current token
   */
  private int parseIndex;
  private Prepared currentPrepared;
  private ArrayList parameters;
  private ArrayList expectedList;
  private boolean rightsChecked;
  private boolean recompileAlways;
  private ArrayList indexedParameterList;
  private int orderInFrom;
  private ArrayList suppliedParameterList;

  /**
   * Checks if this string is a SQL keyword.
   *
   * @param s the token to check
   * @param supportOffsetFetch if OFFSET and FETCH are keywords
   * @return true if it is a keyword
   */
  public static boolean isKeyword(String s, boolean supportOffsetFetch) {
    if (s == null || s.length() == 0) {
      return false;
    }
    return getSaveTokenType(s, supportOffsetFetch) != IDENTIFIER;
  }

  private static int getSaveTokenType(String s, boolean supportOffsetFetch) {
    switch (s.charAt(0)) {
      case 'C':
        if (s.equals("CURRENT_TIMESTAMP")) {
          return CURRENT_TIMESTAMP;
        } else if (s.equals("CURRENT_TIME")) {
          return CURRENT_TIME;
        } else if (s.equals("CURRENT_DATE")) {
          return CURRENT_DATE;
        }
        return getKeywordOrIdentifier(s, "CROSS", KEYWORD);
      case 'D':
        return getKeywordOrIdentifier(s, "DISTINCT", KEYWORD);
      case 'E':
        if ("EXCEPT".equals(s)) {
          return KEYWORD;
        }
        return getKeywordOrIdentifier(s, "EXISTS", KEYWORD);
      case 'F':
        if ("FROM".equals(s)) {
          return KEYWORD;
        } else if ("FOR".equals(s)) {
          return KEYWORD;
        } else if ("FULL".equals(s)) {
          return KEYWORD;
        } else if (supportOffsetFetch && "FETCH".equals(s)) {
          return KEYWORD;
        }
        return getKeywordOrIdentifier(s, "FALSE", FALSE);
      case 'G':
        return getKeywordOrIdentifier(s, "GROUP", KEYWORD);
      case 'H':
        return getKeywordOrIdentifier(s, "HAVING", KEYWORD);
      case 'I':
        if ("INNER".equals(s)) {
          return KEYWORD;
        } else if ("INTERSECT".equals(s)) {
          return KEYWORD;
        }
        return getKeywordOrIdentifier(s, "IS", KEYWORD);
      case 'J':
        return getKeywordOrIdentifier(s, "JOIN", KEYWORD);
      case 'L':
        if ("LIMIT".equals(s)) {
          return KEYWORD;
        }
        return getKeywordOrIdentifier(s, "LIKE", KEYWORD);
      case 'M':
        return getKeywordOrIdentifier(s, "MINUS", KEYWORD);
      case 'N':
        if ("NOT".equals(s)) {
          return KEYWORD;
        } else if ("NATURAL".equals(s)) {
          return KEYWORD;
        }
        return getKeywordOrIdentifier(s, "NULL", NULL);
      case 'O':
        if ("ON".equals(s)) {
          return KEYWORD;
        } else if (supportOffsetFetch && "OFFSET".equals(s)) {
          return KEYWORD;
        }
        return getKeywordOrIdentifier(s, "ORDER", KEYWORD);
      case 'P':
        return getKeywordOrIdentifier(s, "PRIMARY", KEYWORD);
      case 'R':
        return getKeywordOrIdentifier(s, "ROWNUM", ROWNUM);
      case 'S':
        if (s.equals("SYSTIMESTAMP")) {
          return CURRENT_TIMESTAMP;
        } else if (s.equals("SYSTIME")) {
          return CURRENT_TIME;
        } else if (s.equals("SYSDATE")) {
          return CURRENT_TIMESTAMP;
        }
        return getKeywordOrIdentifier(s, "SELECT", KEYWORD);
      case 'T':
        if ("TODAY".equals(s)) {
          return CURRENT_DATE;
        }
        return getKeywordOrIdentifier(s, "TRUE", TRUE);
      case 'U':
        if ("UNIQUE".equals(s)) {
          return KEYWORD;
        }
        return getKeywordOrIdentifier(s, "UNION", KEYWORD);
      case 'W':
        if ("WITH".equals(s)) {
          return KEYWORD;
        }
        return getKeywordOrIdentifier(s, "WHERE", KEYWORD);
      default:
        return IDENTIFIER;
    }
  }

  private static int getKeywordOrIdentifier(String s1, String s2,
      int keywordType) {
    if (s1.equals(s2)) {
      return keywordType;
    }
    return IDENTIFIER;
  }

  private static int getCompareType(int tokenType) {
    switch (tokenType) {
      case EQUAL:
        return Comparison.EQUAL;
      case BIGGER_EQUAL:
        return Comparison.BIGGER_EQUAL;
      case BIGGER:
        return Comparison.BIGGER;
      case SMALLER:
        return Comparison.SMALLER;
      case SMALLER_EQUAL:
        return Comparison.SMALLER_EQUAL;
      case NOT_EQUAL:
        return Comparison.NOT_EQUAL;
      case SPATIAL_INTERSECTS:
        return Comparison.SPATIAL_INTERSECTS;
      default:
        return -1;
    }
  }

  /**
   * Add double quotes around an identifier if required.
   *
   * @param s the identifier
   * @return the quoted identifier
   */
  public static String quoteIdentifier(String s) {
    if (s == null || s.length() == 0) {
      return "\"\"";
    }
    char c = s.charAt(0);
    // lowercase a-z is quoted as well
    if ((!Character.isLetter(c) && c != '_') || Character.isLowerCase(c)) {
      return StringUtils.quoteIdentifier(s);
    }
    for (int i = 1, length = s.length(); i < length; i++) {
      c = s.charAt(i);
      if ((!Character.isLetterOrDigit(c) && c != '_') ||
          Character.isLowerCase(c)) {
        return StringUtils.quoteIdentifier(s);
      }
    }
    if (isKeyword(s, true)) {
      return StringUtils.quoteIdentifier(s);
    }
    return s;
  }

  /**
   * Parse the statement, but don't prepare it for execution.
   *
   * @param sql the SQL statement to parse
   * @return the prepared object
   */
  public Prepared parse(String sql) {
    Prepared p;
    try {
      // first, try the fast variant
      p = parse(sql, false);
    } catch (ParseException e) {
      if (e.getErrorCode() == ErrorCode.SYNTAX_ERROR_1) {
        // now, get the detailed exception
        p = parse(sql, true);
      } else {
        throw e.addSQL(sql);
      }
    }
    p.setPrepareAlways(recompileAlways);
    p.setParameterList(parameters);
    return p;
  }

  private Prepared parse(String sql, boolean withExpectedList) {
    initialize(sql);
    if (withExpectedList) {
      expectedList = New.arrayList();
    } else {
      expectedList = null;
    }
    parameters = New.arrayList();
    currentPrepared = null;
    recompileAlways = false;
    indexedParameterList = suppliedParameterList;
    read();
    return parsePrepared();
  }

  private Prepared parsePrepared() {
    int start = lastParseIndex;
    Prepared c = null;
    String token = currentToken;
    if (token.length() == 0) {
      c = new NoOperation();
    } else {
      char first = token.charAt(0);
      switch (first) {
        case 'd':
        case 'D':
          if (readIf("DELETE")) {
            c = parseDelete();
          }
          break;
        case 'i':
        case 'I':
          if (readIf("INSERT")) {
            c = parseInsert();
          }
          break;
        case 'u':
        case 'U':
          if (readIf("UPDATE")) {
            c = parseUpdate();
          }
          break;
        case ';':
          c = new NoOperation();
          break;
        default:
          throw getSyntaxError();
      }
      if (indexedParameterList != null) {
        for (int i = 0, size = indexedParameterList.size();
            i < size; i++) {
          if (indexedParameterList.get(i) == null) {
            indexedParameterList.set(i, new Parameter(i));
          }
        }
        parameters = indexedParameterList;
      }
      if (readIf("{")) {
        do {
          int index = (int) readLong() - 1;
          if (index < 0 || index >= parameters.size()) {
            throw getSyntaxError();
          }
          Parameter p = parameters.get(index);
          if (p == null) {
            throw getSyntaxError();
          }
          read(":");
          Expression expr = readExpression();
          expr = expr.optimize();
          p.setValue(expr.getValue());
        } while (readIf(","));
        read("}");
        for (Parameter p : parameters) {
          p.checkSet();
        }
        parameters.clear();
      }
    }
    if (c == null) {
      throw getSyntaxError();
    }
    setSQL(c, null, start);
    return c;
  }

  private ParseException getSyntaxError() {
    if (expectedList == null || expectedList.size() == 0) {
      return ParseException.getSyntaxError(sqlCommand, parseIndex);
    }
    StatementBuilder buff = new StatementBuilder();
    for (String e : expectedList) {
      buff.appendExceptFirst(", ");
      buff.append(e);
    }
    return ParseException.getSyntaxError(sqlCommand, parseIndex,
        buff.toString());
  }

  private Update parseUpdate() {
    Update command = new Update();
    currentPrepared = command;
    int start = lastParseIndex;
    command.setTable(readTableOrView());
    read();
    read("SET");
    if (readIf("(")) {
      ArrayList columns = New.arrayList();
      do {
        Column column = readTableColumn();
        columns.add(column);
      } while (readIf(","));
      read(")");
      read("=");
      Expression expression = readExpression();
      if (columns.size() == 1) {
        // the expression is parsed as a simple value
        command.setAssignment(columns.get(0), expression);
      } else {
        for (int i = 0, size = columns.size(); i < size; i++) {
          Column column = columns.get(i);
        }
      }
    } else {
      do {
        Column column = readTableColumn();
        read("=");
        Expression expression;
        if (readIf("DEFAULT")) {
          expression = ValueExpression.getDefault();
        } else {
          expression = readExpression();
        }
        command.setAssignment(column, expression);
      } while (readIf(","));
    }
    if (readIf("WHERE")) {
      Expression condition = readExpression();
      command.setCondition(condition);
    }
    if (readIf("LIMIT")) {
      Expression limit = readTerm().optimize();
      command.setLimit(limit);
    }
    setSQL(command, "UPDATE", start);
    return command;
  }

  private Column readTableColumn() {
    String tableAlias = null;
    String columnName = readColumnIdentifier();
    if (readIf(".")) {
      tableAlias = columnName;
      columnName = readColumnIdentifier();
      if (readIf(".")) {
        String schema = tableAlias;
        tableAlias = columnName;
        columnName = readColumnIdentifier();
        if (readIf(".")) {
          String catalogName = schema;
          schema = tableAlias;
          tableAlias = columnName;
          columnName = readColumnIdentifier();
        }
      } else {
        return new Column(columnName, tableAlias);
      }
    }
    return new Column(columnName);
  }


  private Delete parseDelete() {
    Delete command = new Delete();
    Expression limit = null;
    if (readIf("TOP")) {
      limit = readTerm().optimize();
    }
    currentPrepared = command;
    int start = lastParseIndex;
    readIf("FROM");
    command.setTable(readTableOrView());
    read();
    if (readIf("WHERE")) {
      Expression condition = readExpression();
      command.setCondition(condition);
    }
    if (readIf("LIMIT") && limit == null) {
      limit = readTerm().optimize();
    }
    command.setLimit(limit);
    setSQL(command, "DELETE", start);
    return command;
  }

  private String[] parseColumnList() {
    ArrayList columns = New.arrayList();
    do {
      String columnName = readColumnIdentifier();
      columns.add(columnName);
    } while (readIfMore());
    return columns.toArray(new String[columns.size()]);
  }

  private boolean readIfMore() {
    if (readIf(",")) {
      return !readIf(")");
    }
    read(")");
    return false;
  }

  private Prepared parseShow() {
    return null;
  }

  private boolean isSelect() {
    int start = lastParseIndex;
    while (readIf("(")) {
      // need to read ahead, it could be a nested union:
      // ((select 1) union (select 1))
    }
    boolean select = isToken("SELECT") || isToken("FROM") || isToken("WITH");
    parseIndex = start;
    read();
    return select;
  }

  private Insert parseInsert() {
    Insert command = new Insert();
    currentPrepared = command;
    read("INTO");
    Table table = readTableOrView();
    command.setTable(table);
    if (readIf("(")) {
      if (isSelect()) {
        throw getSyntaxError();
      }
      command.setColumns(parseColumnList());
    }
    if (readIf("SORTED")) {
      throw getSyntaxError();
    }
    if (readIf("DEFAULT")) {
      throw getSyntaxError();
    } else if (readIf("VALUES")) {
      read("(");
      do {
        ArrayList values = New.arrayList();
        if (!readIf(")")) {
          do {
            if (readIf("DEFAULT")) {
              values.add(null);
            } else {
              values.add(readExpression());
            }
          } while (readIfMore());
        }
        command.addRow(values.toArray(new Expression[values.size()]));
        // the following condition will allow (..),; and (..);
      } while (readIf(",") && readIf("("));
    } else if (readIf("SET")) {
      throw getSyntaxError();
    } else {
      throw getSyntaxError();
    }
    return command;
  }


  private String readFromAlias(String alias) {
    if (readIf("AS")) {
      alias = readAliasIdentifier();
    } else if (currentTokenType == IDENTIFIER) {
      // left and right are not keywords (because they are functions as
      // well)
      if (!isToken("LEFT") && !isToken("RIGHT") && !isToken("FULL")) {
        alias = readAliasIdentifier();
      }
    }
    return alias;
  }

  private boolean readIfExists(boolean ifExists) {
    if (readIf("IF")) {
      read("EXISTS");
      ifExists = true;
    }
    return ifExists;
  }

  private Prepared parseComment() {
    throw getSyntaxError();
  }

  private Expression readTermObjectDot(String objectName) {
    Expression expr = readWildcardOrSequenceValue(objectName);
    if (expr != null) {
      return expr;
    }
    String name = readColumnIdentifier();
    if (readIf(".")) {
      String schema = objectName;
      objectName = name;
      expr = readWildcardOrSequenceValue(objectName);
      if (expr != null) {
        return expr;
      }
      name = readColumnIdentifier();
      if (readIf(".")) {
        schema = objectName;
        objectName = name;
        expr = readWildcardOrSequenceValue(objectName);
        if (expr != null) {
          return expr;
        }
        name = readColumnIdentifier();
        return new ExpressionColumn(objectName, name);
      }
      return new ExpressionColumn(objectName, name);
    }
    return new ExpressionColumn(objectName, name);
  }

  private Expression readWildcardOrSequenceValue(String objectName) {
    if (readIf("*")) {
      return new Wildcard(objectName);
    }
    if (readIf("NEXTVAL")) {
      return new SequenceValue(objectName, currentToken);
    } else if (readIf("CURRVAL")) {
      return new SequenceValue(objectName, currentToken);
    }
    return null;
  }

  private Expression readTerm() {
    Expression r;
    switch (currentTokenType) {
      case PARAMETER:
        // there must be no space between ? and the number
        boolean indexed = Character.isDigit(sqlCommandChars[parseIndex]);
        read();
        Parameter p;
        if (indexed && currentTokenType == VALUE &&
            currentValue.getType() == Value.INT) {
          if (indexedParameterList == null) {
            if (parameters == null) {
              // this can occur when parsing expressions only (for
              // example check constraints)
              throw getSyntaxError();
            } else if (parameters.size() > 0) {
              throw ParseException
                  .get(ErrorCode.CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS);
            }
            indexedParameterList = New.arrayList();
          }
          int index = currentValue.getInt() - 1;
          if (index < 0 || index >= Constants.MAX_PARAMETER_INDEX) {
            throw ParseException.getInvalidValueException(
                "parameter index", index);
          }
          if (indexedParameterList.size() <= index) {
            indexedParameterList.ensureCapacity(index + 1);
            while (indexedParameterList.size() <= index) {
              indexedParameterList.add(null);
            }
          }
          p = indexedParameterList.get(index);
          if (p == null) {
            p = new Parameter(index);
            indexedParameterList.set(index, p);
          }
          read();
        } else {
          if (indexedParameterList != null) {
            throw ParseException
                .get(ErrorCode.CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS);
          }
          p = new Parameter(parameters.size());
        }
        parameters.add(p);
        r = p;
        break;
      case KEYWORD:
        throw getSyntaxError();
      case IDENTIFIER:
        String name = currentToken;
        if (currentTokenQuoted) {
          read();
          if (readIf("(")) {
            //r = readFunction(null, name);
            r = null;
          } else if (readIf(".")) {
            //r = readTermObjectDot(name);
            r = null;
          } else {
            r = new ExpressionColumn(null, name);
          }
        } else {
          read();
          if (readIf(".")) {
            r = readTermObjectDot(name);
            //r = null;
          } else if (currentTokenType == VALUE && currentValue.getType() == Value.STRING) {
            if (equalsToken("DATE", name) ||
                equalsToken("D", name)) {
              String date = currentValue.getString();
              read();
              r = ValueExpression.get(ValueDate.parse(date));
            } else if (equalsToken("TIME", name) || equalsToken("T", name)) {
              String time = currentValue.getString();
              read();
              r = ValueExpression.get(ValueTime.parse(time));
            } else if (equalsToken("TIMESTAMP", name) || equalsToken("TS", name)) {
              String timestamp = currentValue.getString();
              read();
              r = ValueExpression
                  .get(ValueTimestamp.parse(timestamp));
            } else if (equalsToken("X", name)) {
              read();
              byte[] buffer = StringUtils
                  .convertHexToBytes(currentValue.getString());
              r = ValueExpression.get(ValueBytes.getNoCopy(buffer));
            } else if (equalsToken("E", name)) {
              String text = currentValue.getString();
              // the PostgreSQL ODBC driver uses
              // LIKE E'PROJECT\\_DATA' instead of LIKE
              // 'PROJECT\_DATA'
              // N: SQL-92 "National Language" strings
              text = StringUtils.replaceAll(text, "\\\\", "\\");
              read();
              r = ValueExpression.get(ValueString.get(text));
            } else if (equalsToken("N", name)) {
              // SQL-92 "National Language" strings
              String text = currentValue.getString();
              read();
              r = ValueExpression.get(ValueString.get(text));
            } else {
              r = new ExpressionColumn(null, name);
            }
          } else {
            r = new ExpressionColumn(null, name);
          }
        }
        break;
      case MINUS:
        read();
        if (currentTokenType == VALUE) {
          r = ValueExpression.get(currentValue.negate());
          /*if (r.getType() == Value.LONG &&
              r.getValue().getLong() == Integer.MIN_VALUE) {
            // convert Integer.MIN_VALUE to type 'int'
            // (Integer.MAX_VALUE+1 is of type 'long')
            r = ValueExpression.get(ValueInt.get(Integer.MIN_VALUE));
          } else if (r.getType() == Value.DECIMAL &&
              r.getValue().getBigDecimal()
                  .compareTo(ValueLong.MIN_BD) == 0) {
            // convert Long.MIN_VALUE to type 'long'
            // (Long.MAX_VALUE+1 is of type 'decimal')
            r = ValueExpression.get(ValueLong.get(Long.MIN_VALUE));
          }*/
          read();
        } else {
          r = new Operation(Operation.NEGATE, readTerm(), null);
        }
        break;
      case PLUS:
        read();
        r = readTerm();
        break;
      case OPEN:
        read();
        if (readIf(")")) {
          r = new ExpressionList(new Expression[0]);
        } else {
          r = readExpression();
          if (readIf(",")) {
            ArrayList list = New.arrayList();
            list.add(r);
            while (!readIf(")")) {
              r = readExpression();
              list.add(r);
              if (!readIf(",")) {
                read(")");
                break;
              }
            }
            Expression[] array = new Expression[list.size()];
            list.toArray(array);
            r = new ExpressionList(array);
          } else {
            read(")");
          }
        }
        break;
      case TRUE:
        read();
        r = ValueExpression.get(ValueBoolean.get(true));
        break;
      case FALSE:
        read();
        r = ValueExpression.get(ValueBoolean.get(false));
        break;
      case NULL:
        read();
        r = ValueExpression.getNull();
        break;
      case VALUE:
        r = ValueExpression.get(currentValue);
        read();
        break;
      default:
        throw getSyntaxError();
    }

    return r;
  }


  private Table getDualTable(boolean noColumns) {
    return null;
  }

  private void setSQL(Prepared command, String start, int startIndex) {
    String sql = originalSQL.substring(startIndex, lastParseIndex).trim();
    if (start != null) {
      sql = start + " " + sql;
    }
    command.setSQL(sql);
  }

  private Expression readExpression() {
    Expression r = readAnd();
    while (readIf("OR")) {
      r = new ConditionAndOr(ConditionAndOr.OR, r, readAnd());
    }
    return r;
  }

  private Expression readAnd() {
    Expression r = readCondition();
    while (readIf("AND")) {
      r = new ConditionAndOr(ConditionAndOr.AND, r, readCondition());
    }
    return r;
  }

  private Expression readCondition() {
    if (readIf("NOT")) {
      return new ConditionNot(readCondition());
    }
    if (readIf("EXISTS")) {
      throw getSyntaxError();
    }
    Expression r = readConcat();
    while (true) {
      // special case: NOT NULL is not part of an expression (as in CREATE
      // TABLE TEST(ID INT DEFAULT 0 NOT NULL))
      int backup = parseIndex;
      boolean not = false;
      if (readIf("NOT")) {
        not = true;
        if (isToken("NULL")) {
          // this really only works for NOT NULL!
          parseIndex = backup;
          currentToken = "NOT";
          break;
        }
      }
      if (readIf("LIKE")) {
        Expression b = readConcat();
        Expression esc = null;
        if (readIf("ESCAPE")) {
          esc = readConcat();
        }
        recompileAlways = true;
        r = new CompareLike(r, b, esc, false);
      } else if (readIf("IN")) {

        throw getSyntaxError();

      } else if (readIf("BETWEEN")) {
        Expression low = readConcat();
        read("AND");
        Expression high = readConcat();
        Expression condLow = new Comparison(
            Comparison.SMALLER_EQUAL, low, r);
        Expression condHigh = new Comparison(
            Comparison.BIGGER_EQUAL, high, r);
        r = new ConditionAndOr(ConditionAndOr.AND, condLow, condHigh);
      } else {
        int compareType = getCompareType(currentTokenType);
        if (compareType < 0) {
          break;
        }
        read();
        Expression right = readConcat();
        r = new Comparison(compareType, r, right);
      }
      if (not) {
        r = new ConditionNot(r);
      }
    }
    return r;
  }

  private Expression readConcat() {
    Expression r = readSum();
    while (true) {
      if (readIf("||")) {
        r = new Operation(Operation.CONCAT, r, readSum());
      } else {
        return r;
      }
    }
  }

  private Expression readSum() {
    Expression r = readFactor();
    while (true) {
      if (readIf("+")) {
        r = new Operation(Operation.PLUS, r, readFactor());
      } else if (readIf("-")) {
        r = new Operation(Operation.MINUS, r, readFactor());
      } else {
        return r;
      }
    }
  }

  private Expression readFactor() {
    Expression r = readTerm();
    while (true) {
      if (readIf("*")) {
        r = new Operation(Operation.MULTIPLY, r, readTerm());
      } else if (readIf("/")) {
        r = new Operation(Operation.DIVIDE, r, readTerm());
      } else if (readIf("%")) {
        r = new Operation(Operation.MODULUS, r, readTerm());
      } else {
        return r;
      }
    }
  }

  private int readPositiveInt() {
    int v = readInt();
    if (v < 0) {
      throw ParseException.getInvalidValueException("positive integer", v);
    }
    return v;
  }

  private int readInt() {
    boolean minus = false;
    if (currentTokenType == MINUS) {
      minus = true;
      read();
    } else if (currentTokenType == PLUS) {
      read();
    }
    if (currentTokenType != VALUE) {
      throw ParseException.getSyntaxError(sqlCommand, parseIndex, "integer");
    }
    if (minus) {
      // must do that now, otherwise Integer.MIN_VALUE would not work
      currentValue = currentValue.negate();
    }
    int i = currentValue.getInt();
    read();
    return i;
  }

  private long readLong() {
    boolean minus = false;
    if (currentTokenType == MINUS) {
      minus = true;
      read();
    } else if (currentTokenType == PLUS) {
      read();
    }
    if (currentTokenType != VALUE) {
      throw ParseException.getSyntaxError(sqlCommand, parseIndex, "long");
    }
    if (minus) {
      // must do that now, otherwise Long.MIN_VALUE would not work
      currentValue = currentValue.negate();
    }
    long i = currentValue.getLong();
    read();
    return i;
  }

  private boolean readBooleanSetting() {
    if (currentTokenType == VALUE) {
      boolean result = currentValue.getBoolean().booleanValue();
      read();
      return result;
    }
    if (readIf("TRUE") || readIf("ON")) {
      return true;
    } else if (readIf("FALSE") || readIf("OFF")) {
      return false;
    } else {
      throw getSyntaxError();
    }
  }

  private String readString() {
    /*Expression expr = readExpression().optimize();
    if (!(expr instanceof ValueExpression)) {
      throw ParseException.getSyntaxError(sqlCommand, parseIndex, "string");
    }
    return expr.getValue().getString();*/
    return null;
  }

  private String readIdentifierWithSchema() {
    if (currentTokenType != IDENTIFIER) {
      throw ParseException.getSyntaxError(sqlCommand, parseIndex, "identifier");
    }
    String s = currentToken;
    read();
    currentSchema = s;
    if (readIf(".")) {
      if (currentTokenType != IDENTIFIER) {
        throw ParseException.getSyntaxError(sqlCommand, parseIndex,
            "identifier");
      }
      s = currentToken;
      read();
    } else {
      currentSchema = "";
    }
    return s;
  }

  private String[] readIdentifierAliasWithSchema() {
    if (currentTokenType != IDENTIFIER) {
      throw ParseException.getSyntaxError(sqlCommand, parseIndex, "identifier");
    }
    String s = currentToken;
    String alias = "";
    read();
    currentSchema = s;
    if (readIf(".")) {
      if (currentTokenType != IDENTIFIER) {
        throw ParseException.getSyntaxError(sqlCommand, parseIndex,
            "identifier");
      }
      s = currentToken;
      read();
    } else {
      if (currentTokenType == IDENTIFIER) {
        alias = currentToken;
      }
      currentSchema = "";
    }
    return new String[]{s, alias};
  }

  private String readAliasIdentifier() {
    return readColumnIdentifier();
  }

  private String readUniqueIdentifier() {
    return readColumnIdentifier();
  }

  private String readColumnIdentifier() {
    if (currentTokenType != IDENTIFIER) {
      throw ParseException.getSyntaxError(sqlCommand, parseIndex,
          "identifier");
    }
    String s = currentToken;
    read();
    return s;
  }

  private void read(String expected) {
    if (currentTokenQuoted || !equalsToken(expected, currentToken)) {
      addExpected(expected);
      throw getSyntaxError();
    }
    read();
  }

  private boolean readIf(String token) {
    if (!currentTokenQuoted && equalsToken(token, currentToken)) {
      read();
      return true;
    }
    addExpected(token);
    return false;
  }

  private boolean isToken(String token) {
    boolean result = equalsToken(token, currentToken) &&
        !currentTokenQuoted;
    if (result) {
      return true;
    }
    addExpected(token);
    return false;
  }

  private boolean equalsToken(String a, String b) {
    if (a == null) {
      return b == null;
    } else if (a.equals(b)) {
      return true;
    } else if (!identifiersToUpper && a.equalsIgnoreCase(b)) {
      return true;
    }
    return false;
  }

  private void addExpected(String token) {
    if (expectedList != null) {
      expectedList.add(token);
    }
  }

  private void read() {
    currentTokenQuoted = false;
    if (expectedList != null) {
      expectedList.clear();
    }
    int[] types = characterTypes;
    lastParseIndex = parseIndex;
    int i = parseIndex;
    int type = types[i];
    while (type == 0) {
      type = types[++i];
    }
    int start = i;
    char[] chars = sqlCommandChars;
    char c = chars[i++];
    currentToken = "";
    switch (type) {
      case CHAR_NAME:
        while (true) {
          type = types[i];
          if (type != CHAR_NAME && type != CHAR_VALUE) {
            break;
          }
          i++;
        }
        currentToken = StringUtils.cache(sqlCommand.substring(
            start, i));
        currentTokenType = getTokenType(currentToken);
        parseIndex = i;
        return;
      case CHAR_QUOTED: {
        String result = null;
        while (true) {
          for (int begin = i; ; i++) {
            if (chars[i] == '\"') {
              if (result == null) {
                result = sqlCommand.substring(begin, i);
              } else {
                result += sqlCommand.substring(begin - 1, i);
              }
              break;
            }
          }
          if (chars[++i] != '\"') {
            break;
          }
          i++;
        }
        currentToken = StringUtils.cache(result);
        parseIndex = i;
        currentTokenQuoted = true;
        currentTokenType = IDENTIFIER;
        return;
      }
      case CHAR_SPECIAL_2:
        if (types[i] == CHAR_SPECIAL_2) {
          i++;
        }
        currentToken = sqlCommand.substring(start, i);
        currentTokenType = getSpecialType(currentToken);
        parseIndex = i;
        return;
      case CHAR_SPECIAL_1:
        currentToken = sqlCommand.substring(start, i);
        currentTokenType = getSpecialType(currentToken);
        parseIndex = i;
        return;
      case CHAR_VALUE:
        if (c == '0' && chars[i] == 'X') {
          // hex number
          long number = 0;
          start += 2;
          i++;
          while (true) {
            c = chars[i];
            if ((c < '0' || c > '9') && (c < 'A' || c > 'F')) {
              checkLiterals(false);
              currentValue = ValueInt.get((int) number);
              currentTokenType = VALUE;
              currentToken = "0";
              parseIndex = i;
              return;
            }
            number = (number << 4) + c -
                (c >= 'A' ? ('A' - 0xa) : ('0'));
            if (number > Integer.MAX_VALUE) {
              readHexDecimal(start, i);
              return;
            }
            i++;
          }
        }
        long number = c - '0';
        while (true) {
          c = chars[i];
          if (c < '0' || c > '9') {
            if (c == '.' || c == 'E' || c == 'L') {
              readDecimal(start, i);
              break;
            }
            checkLiterals(false);
            currentValue = ValueInt.get((int) number);
            currentTokenType = VALUE;
            currentToken = "0";
            parseIndex = i;
            break;
          }
          number = number * 10 + (c - '0');
          if (number > Integer.MAX_VALUE) {
            readDecimal(start, i);
            break;
          }
          i++;
        }
        return;
      case CHAR_DOT:
        if (types[i] != CHAR_VALUE) {
          currentTokenType = KEYWORD;
          currentToken = ".";
          parseIndex = i;
          return;
        }
        readDecimal(i - 1, i);
        return;
      case CHAR_STRING: {
        String result = null;
        while (true) {
          for (int begin = i; ; i++) {
            if (chars[i] == '\'') {
              if (result == null) {
                result = sqlCommand.substring(begin, i);
              } else {
                result += sqlCommand.substring(begin - 1, i);
              }
              break;
            }
          }
          if (chars[++i] != '\'') {
            break;
          }
          i++;
        }
        currentToken = "'";
        checkLiterals(true);
        currentValue = ValueString.get(StringUtils.cache(result));
        parseIndex = i;
        currentTokenType = VALUE;
        return;
      }
      case CHAR_DOLLAR_QUOTED_STRING: {
        String result = null;
        int begin = i - 1;
        while (types[i] == CHAR_DOLLAR_QUOTED_STRING) {
          i++;
        }
        result = sqlCommand.substring(begin, i);
        currentToken = "'";
        checkLiterals(true);
        currentValue = ValueString.get(StringUtils.cache(result));
        parseIndex = i;
        currentTokenType = VALUE;
        return;
      }
      case CHAR_END:
        currentToken = "";
        currentTokenType = END;
        parseIndex = i;
        return;
      default:
        throw getSyntaxError();
    }
  }

  private void checkLiterals(boolean text) {

  }

  private void readHexDecimal(int start, int i) {
    char[] chars = sqlCommandChars;
    char c;
    do {
      c = chars[++i];
    } while ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'));
    parseIndex = i;
    String sub = sqlCommand.substring(start, i);
    BigDecimal bd = new BigDecimal(new BigInteger(sub, 16));
    checkLiterals(false);
    currentValue = ValueDecimal.get(bd);
    currentTokenType = VALUE;
  }

  private void readDecimal(int start, int i) {
    char[] chars = sqlCommandChars;
    int[] types = characterTypes;
    // go until the first non-number
    while (true) {
      int t = types[i];
      if (t != CHAR_DOT && t != CHAR_VALUE) {
        break;
      }
      i++;
    }
    boolean containsE = false;
    if (chars[i] == 'E' || chars[i] == 'e') {
      containsE = true;
      i++;
      if (chars[i] == '+' || chars[i] == '-') {
        i++;
      }
      if (types[i] != CHAR_VALUE) {
        throw getSyntaxError();
      }
      while (types[++i] == CHAR_VALUE) {
        // go until the first non-number
      }
    }
    parseIndex = i;
    String sub = sqlCommand.substring(start, i);
    checkLiterals(false);
    if (!containsE && sub.indexOf('.') < 0) {
      BigInteger bi = new BigInteger(sub);
      if (bi.compareTo(ValueLong.MAX) <= 0) {
        // parse constants like "10000000L"
        if (chars[i] == 'L') {
          parseIndex++;
        }
        currentValue = ValueLong.get(bi.longValue());
        currentTokenType = VALUE;
        return;
      }
    }
    BigDecimal bd;
    try {
      bd = new BigDecimal(sub);
    } catch (NumberFormatException e) {
      throw ParseException.get(ErrorCode.DATA_CONVERSION_ERROR_1, e, sub);
    }
    currentValue = ValueDecimal.get(bd);
    currentTokenType = VALUE;
  }

  private void initialize(String sql) {
    if (sql == null) {
      sql = "";
    }
    originalSQL = sql;
    sqlCommand = sql;
    int len = sql.length() + 1;
    char[] command = new char[len];
    int[] types = new int[len];
    len--;
    sql.getChars(0, len, command, 0);
    boolean changed = false;
    command[len] = ' ';
    int startLoop = 0;
    int lastType = 0;
    for (int i = 0; i < len; i++) {
      char c = command[i];
      int type = 0;
      switch (c) {
        case '/':
          if (command[i + 1] == '*') {
            // block comment
            changed = true;
            command[i] = ' ';
            command[i + 1] = ' ';
            startLoop = i;
            i += 2;
            checkRunOver(i, len, startLoop);
            while (command[i] != '*' || command[i + 1] != '/') {
              command[i++] = ' ';
              checkRunOver(i, len, startLoop);
            }
            command[i] = ' ';
            command[i + 1] = ' ';
            i++;
          } else if (command[i + 1] == '/') {
            // single line comment
            changed = true;
            startLoop = i;
            while (true) {
              c = command[i];
              if (c == '\n' || c == '\r' || i >= len - 1) {
                break;
              }
              command[i++] = ' ';
              checkRunOver(i, len, startLoop);
            }
          } else {
            type = CHAR_SPECIAL_1;
          }
          break;
        case '-':
          if (command[i + 1] == '-') {
            // single line comment
            changed = true;
            startLoop = i;
            while (true) {
              c = command[i];
              if (c == '\n' || c == '\r' || i >= len - 1) {
                break;
              }
              command[i++] = ' ';
              checkRunOver(i, len, startLoop);
            }
          } else {
            type = CHAR_SPECIAL_1;
          }
          break;
        case '$':
          if (command[i + 1] == '$' && (i == 0 || command[i - 1] <= ' ')) {
            // dollar quoted string
            changed = true;
            command[i] = ' ';
            command[i + 1] = ' ';
            startLoop = i;
            i += 2;
            checkRunOver(i, len, startLoop);
            while (command[i] != '$' || command[i + 1] != '$') {
              types[i++] = CHAR_DOLLAR_QUOTED_STRING;
              checkRunOver(i, len, startLoop);
            }
            command[i] = ' ';
            command[i + 1] = ' ';
            i++;
          } else {
            if (lastType == CHAR_NAME || lastType == CHAR_VALUE) {
              // $ inside an identifier is supported
              type = CHAR_NAME;
            } else {
              // but not at the start, to support PostgreSQL $1
              type = CHAR_SPECIAL_1;
            }
          }
          break;
        case '(':
        case ')':
        case '{':
        case '}':
        case '*':
        case ',':
        case ';':
        case '+':
        case '%':
        case '?':
        case '@':
        case ']':
          type = CHAR_SPECIAL_1;
          break;
        case '!':
        case '<':
        case '>':
        case '|':
        case '=':
        case ':':
        case '&':
        case '~':
          type = CHAR_SPECIAL_2;
          break;
        case '.':
          type = CHAR_DOT;
          break;
        case '\'':
          type = types[i] = CHAR_STRING;
          startLoop = i;
          while (command[++i] != '\'') {
            checkRunOver(i, len, startLoop);
          }
          break;
        case '[':

          type = CHAR_SPECIAL_1;

          break;
        case '`':
          // MySQL alias for ", but not case sensitive
          command[i] = '"';
          changed = true;
          type = types[i] = CHAR_QUOTED;
          startLoop = i;
          while (command[++i] != '`') {
            checkRunOver(i, len, startLoop);
            c = command[i];
            command[i] = Character.toUpperCase(c);
          }
          command[i] = '"';
          break;
        case '\"':
          type = types[i] = CHAR_QUOTED;
          startLoop = i;
          while (command[++i] != '\"') {
            checkRunOver(i, len, startLoop);
          }
          break;
        case '_':
          type = CHAR_NAME;
          break;
        case '#':

          break;

        default:
          if (c >= 'a' && c <= 'z') {
            if (identifiersToUpper) {
              command[i] = (char) (c - ('a' - 'A'));
              changed = true;
            }
            type = CHAR_NAME;
          } else if (c >= 'A' && c <= 'Z') {
            type = CHAR_NAME;
          } else if (c >= '0' && c <= '9') {
            type = CHAR_VALUE;
          } else {
            if (c <= ' ' || Character.isSpaceChar(c)) {
              // whitespace
            } else if (Character.isJavaIdentifierPart(c)) {
              type = CHAR_NAME;
              if (identifiersToUpper) {
                char u = Character.toUpperCase(c);
                if (u != c) {
                  command[i] = u;
                  changed = true;
                }
              }
            } else {
              type = CHAR_SPECIAL_1;
            }
          }
      }
      types[i] = type;
      lastType = type;
    }
    sqlCommandChars = command;
    types[len] = CHAR_END;
    characterTypes = types;
    if (changed) {
      sqlCommand = new String(command);
    }
    parseIndex = 0;
  }

  private void checkRunOver(int i, int len, int startLoop) {
    if (i >= len) {
      parseIndex = startLoop;
      throw getSyntaxError();
    }
  }

  private int getSpecialType(String s) {
    char c0 = s.charAt(0);
    if (s.length() == 1) {
      switch (c0) {
        case '?':
        case '$':
          return PARAMETER;
        case '@':
          return AT;
        case '+':
          return PLUS;
        case '-':
          return MINUS;
        case '{':
        case '}':
        case '*':
        case '/':
        case '%':
        case ';':
        case ',':
        case ':':
        case '[':
        case ']':
        case '~':
          return KEYWORD;
        case '(':
          return OPEN;
        case ')':
          return CLOSE;
        case '<':
          return SMALLER;
        case '>':
          return BIGGER;
        case '=':
          return EQUAL;
        default:
          break;
      }
    } else if (s.length() == 2) {
      switch (c0) {
        case ':':
          if ("::".equals(s)) {
            return KEYWORD;
          } else if (":=".equals(s)) {
            return KEYWORD;
          }
          break;
        case '>':
          if (">=".equals(s)) {
            return BIGGER_EQUAL;
          }
          break;
        case '<':
          if ("<=".equals(s)) {
            return SMALLER_EQUAL;
          } else if ("<>".equals(s)) {
            return NOT_EQUAL;
          }
          break;
        case '!':
          if ("!=".equals(s)) {
            return NOT_EQUAL;
          } else if ("!~".equals(s)) {
            return KEYWORD;
          }
          break;
        case '|':
          if ("||".equals(s)) {
            return STRING_CONCAT;
          }
          break;
        case '&':
          if ("&&".equals(s)) {
            return SPATIAL_INTERSECTS;
          }
          break;
      }
    }
    throw getSyntaxError();
  }

  private int getTokenType(String s) {
    int len = s.length();
    if (len == 0) {
      throw getSyntaxError();
    }
    if (!identifiersToUpper) {
      // if not yet converted to uppercase, do it now
      s = StringUtils.toUpperEnglish(s);
    }
    return getSaveTokenType(s, true);
  }

  private boolean isKeyword(String s) {
    if (!identifiersToUpper) {
      // if not yet converted to uppercase, do it now
      s = StringUtils.toUpperEnglish(s);
    }
    return isKeyword(s, false);
  }

  private Column parseColumnForTable(String columnName,
      boolean defaultNullable) {
    Column column;
    boolean isIdentity = false;
    if (readIf("IDENTITY") || readIf("BIGSERIAL")) {
      column = new Column(columnName);
      //column.setOriginalSQL("IDENTITY");
      parseAutoIncrement(column);

    } else if (readIf("SERIAL")) {
      column = new Column(columnName);
      //column.setOriginalSQL("SERIAL");
      parseAutoIncrement(column);

    } else {
      column = parseColumnWithType(columnName);
    }

    if (readIf("AS")) {
      if (isIdentity) {
        getSyntaxError();
      }
    } else if (readIf("GENERATED")) {
      if (!readIf("ALWAYS")) {
        read("BY");
        read("DEFAULT");
      }
      read("AS");
      read("IDENTITY");
      long start = 1, increment = 1;
      if (readIf("(")) {
        read("START");
        readIf("WITH");
        start = readLong();
        readIf(",");
        if (readIf("INCREMENT")) {
          readIf("BY");
          increment = readLong();
        }
        read(")");
      }
    }
    if (readIf("AUTO_INCREMENT") || readIf("BIGSERIAL") || readIf("SERIAL")) {
      parseAutoIncrement(column);
      if (readIf("NOT")) {
        read("NULL");
      }
    } else if (readIf("IDENTITY")) {
      parseAutoIncrement(column);
      if (readIf("NOT")) {
        read("NULL");
      }
    }
    if (readIf("SELECTIVITY")) {
      int value = readPositiveInt();
    }
    String comment = readCommentIf();
    return column;
  }

  private void parseAutoIncrement(Column column) {
    long start = 1, increment = 1;
    if (readIf("(")) {
      start = readLong();
      if (readIf(",")) {
        increment = readLong();
      }
      read(")");
    }
  }

  private String readCommentIf() {
    if (readIf("COMMENT")) {
      readIf("IS");
      return readString();
    }
    return null;
  }

  private Column parseColumnWithType(String columnName) {
    String original = currentToken;
    boolean regular = false;
    if (readIf("LONG")) {
      if (readIf("RAW")) {
        original += " RAW";
      }
    } else if (readIf("DOUBLE")) {
      if (readIf("PRECISION")) {
        original += " PRECISION";
      }
    } else if (readIf("CHARACTER")) {
      if (readIf("VARYING")) {
        original += " VARYING";
      }
    } else if (readIf("TIMESTAMP")) {
      if (readIf("WITH")) {
        // originally we used TIMEZONE, which turns out not to be
        // standards-compliant, but lets keep backwards compatibility
        if (readIf("TIMEZONE")) {
          read("TIMEZONE");
          original += " WITH TIMEZONE";
        } else {
          read("TIME");
          read("ZONE");
          original += " WITH TIME ZONE";
        }
      }
    } else {
      regular = true;
    }
    long precision = -1;
    int displaySize = -1;
    int scale = -1;
    String comment = null;
    Column templateColumn = null;
    DataType dataType;
    if (!identifiersToUpper) {
      original = StringUtils.toUpperEnglish(original);
    }
    dataType = DataType.getTypeByName(original);
    if (dataType == null) {
      throw ParseException.get(ErrorCode.UNKNOWN_DATA_TYPE_1,
          currentToken);
    }
    if (regular) {
      read();
    }
    precision = precision == -1 ? dataType.defaultPrecision : precision;
    displaySize = displaySize == -1 ? dataType.defaultDisplaySize
        : displaySize;
    scale = scale == -1 ? dataType.defaultScale : scale;
    if (dataType.supportsPrecision || dataType.supportsScale) {
      if (readIf("(")) {
        if (!readIf("MAX")) {
          long p = readLong();
          if (readIf("K")) {
            p *= 1024;
          } else if (readIf("M")) {
            p *= 1024 * 1024;
          } else if (readIf("G")) {
            p *= 1024 * 1024 * 1024;
          }
          if (p > Long.MAX_VALUE) {
            p = Long.MAX_VALUE;
          }
          original += "(" + p;
          // Oracle syntax
          readIf("CHAR");
          if (dataType.supportsScale) {
            if (readIf(",")) {
              scale = readInt();
              original += ", " + scale;
            } else {
              // special case: TIMESTAMP(5) actually means
              // TIMESTAMP(23, 5)
              if (dataType.type == Value.TIMESTAMP) {
                scale = MathUtils.convertLongToInt(p);
                p = precision;
              } else {
                scale = 0;
              }
            }
          }
          precision = p;
          displaySize = MathUtils.convertLongToInt(precision);
          original += ")";
        }
        read(")");
      }
    } else if (readIf("(")) {
      // Support for MySQL: INT(11), MEDIUMINT(8) and so on.
      // Just ignore the precision.
      readPositiveInt();
      read(")");
    }
    if (readIf("FOR")) {
      read("BIT");
      read("DATA");
      if (dataType.type == Value.STRING) {
        dataType = DataType.getTypeByName("BINARY");
      }
    }
    // MySQL compatibility
    readIf("UNSIGNED");
    int type = dataType.type;
    if (scale > precision) {
      throw ParseException.get(ErrorCode.INVALID_VALUE_SCALE_PRECISION,
          Integer.toString(scale), Long.toString(precision));
    }
    Column column = new Column(columnName);
    return column;
  }

  private Prepared parseCreate() {
    throw getSyntaxError();
  }


  private void readIfEqualOrTo() {
    if (!readIf("=")) {
      readIf("TO");
    }
  }


  private ScriptCommand parseScript() {
    ScriptCommand command = new ScriptCommand();
    boolean data = true, passwords = true, settings = true;
    boolean dropTables = false, simple = false;
    if (readIf("SIMPLE")) {
      simple = true;
    }
    if (readIf("NODATA")) {
      data = false;
    }
    if (readIf("NOPASSWORDS")) {
      passwords = false;
    }
    if (readIf("NOSETTINGS")) {
      settings = false;
    }
    if (readIf("DROP")) {
      dropTables = true;
    }
    if (readIf("BLOCKSIZE")) {
      long blockSize = readLong();
      command.setLobBlockSize(blockSize);
    }
    command.setData(data);
    command.setPasswords(passwords);
    command.setSettings(settings);
    command.setDrop(dropTables);
    command.setSimple(simple);
    if (readIf("TO")) {
      command.setFileNameExpr(readExpression());
      if (readIf("COMPRESSION")) {
        command.setCompressionAlgorithm(readUniqueIdentifier());
      }
      if (readIf("CIPHER")) {
        command.setCipher(readUniqueIdentifier());
        if (readIf("PASSWORD")) {
          command.setPassword(readExpression());
        }
      }
      if (readIf("CHARSET")) {
        command.setCharset(Charset.forName(readString()));
      }
    }
    if (readIf("SCHEMA")) {
      HashSet schemaNames = New.hashSet();
      do {
        schemaNames.add(readUniqueIdentifier());
      } while (readIf(","));
      command.setSchemaNames(schemaNames);
    } else if (readIf("TABLE")) {
      ArrayList tables = New.arrayList();
      do {
        tables.add(readTableOrView());
      } while (readIf(","));
      command.setTables(tables);
    }
    return command;
  }

  private Table readTableOrView() {
    return readTableOrView(readIdentifierAliasWithSchema(), currentSchema);
  }

  private Table readTableOrView(String[] table, String schema) {
    return new Table(table[0], table[1], schema);
  }

  private Prepared parseAlterTable() {
    throw getSyntaxError();
  }
}