Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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();
}
}