com.caucho.db.sql.Parser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of resin Show documentation
Show all versions of resin Show documentation
Resin Java Application Server
/*
* Copyright (c) 1998-2018 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
* Free SoftwareFoundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.db.sql;
import com.caucho.db.Database;
import com.caucho.db.table.Column;
import com.caucho.db.table.Column.ColumnType;
import com.caucho.db.table.Table;
import com.caucho.db.table.TableFactory;
import com.caucho.inject.Module;
import com.caucho.util.CharBuffer;
import com.caucho.util.IntMap;
import com.caucho.util.L10N;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
@Module
public class Parser {
private static final Logger log
= Logger.getLogger(Parser.class.getName());
private static final L10N L = new L10N(Parser.class);
final static int IDENTIFIER = 128;
final static int INTEGER = IDENTIFIER + 1;
final static int LONG = INTEGER + 1;
final static int DOUBLE = LONG + 1;
final static int STRING = DOUBLE + 1;
final static int TRUE = STRING + 1;
final static int FALSE = TRUE + 1;
final static int UNKNOWN = FALSE + 1;
final static int NULL = UNKNOWN + 1;
final static int EXISTS = NULL + 1;
final static int FROM = EXISTS + 1;
final static int IN = FROM + 1;
final static int SELECT = IN + 1;
final static int DISTINCT = SELECT + 1;
final static int WHERE = SELECT + 1;
final static int AS = WHERE + 1;
final static int ORDER = AS + 1;
final static int GROUP = ORDER + 1;
final static int BY = GROUP + 1;
final static int ASC = BY + 1;
final static int DESC = ASC + 1;
final static int LIMIT = DESC + 1;
final static int OFFSET = LIMIT + 1;
final static int BETWEEN = OFFSET + 1;
final static int LIKE = BETWEEN + 1;
final static int ESCAPE = LIKE + 1;
final static int IS = ESCAPE + 1;
final static int CONCAT = IS + 1;
final static int EQ = CONCAT + 1;
final static int NE = EQ + 1;
final static int LT = NE + 1;
final static int LE = LT + 1;
final static int GT = LE + 1;
final static int GE = GT + 1;
final static int AND = GE + 1;
final static int OR = AND + 1;
final static int NOT = OR + 1;
final static int ARG = NOT + 1;
final static int CREATE = ARG + 1;
final static int TABLE = CREATE + 1;
final static int INSERT = TABLE + 1;
final static int INTO = INSERT + 1;
final static int VALUES = INTO + 1;
final static int DROP = VALUES + 1;
final static int UPDATE = DROP + 1;
final static int SET = UPDATE + 1;
final static int DELETE = SET + 1;
final static int VALIDATE = DELETE + 1;
final static int SHOW = VALIDATE + 1;
final static int CONSTRAINT = SHOW + 1;
final static int UNIQUE = CONSTRAINT + 1;
final static int PRIMARY = UNIQUE + 1;
final static int CHECK = PRIMARY + 1;
final static int FOREIGN = CHECK + 1;
final static int KEY = FOREIGN + 1;
private final static IntMap _reserved;
private Database _database;
private final String _sql;
private final char []_sqlChars;
private final int _sqlLength;
private int _parseIndex;
private final CharBuffer _cb = new CharBuffer();
private String _lexeme;
private int _token;
private ArrayList _params = new ArrayList();
private Query _query;
private AndExpr _andExpr;
private Parser(Database database, String sql)
{
_database = database;
_sql = sql;
_sqlLength = _sql.length();
_sqlChars = new char[_sqlLength];
_sql.getChars(0, _sqlLength, _sqlChars, 0);
}
public static Query parse(Database database, String sql)
throws SQLException
{
Parser parser = new Parser(database, sql);
Query query = parser.parse();
query.bind();
return query;
}
public static Expr parseExpr(Database database, String sql)
throws SQLException
{
Parser parser = new Parser(database, sql);
Expr expr = parser.parseExpr();
return expr.bind(null);
}
/**
* Parses the query.
*/
private Query parse()
throws SQLException
{
int token = scanToken();
switch (token) {
case SELECT:
return parseSelect();
case CREATE:
return parseCreate();
case INSERT:
return parseInsert();
case DELETE:
return parseDelete();
case VALIDATE:
return parseValidate();
case DROP:
return parseDrop();
case UPDATE:
return parseUpdate();
//case SHOW:
//return parseShow();
default:
throw new SQLParseException(L.l("unknown query at {0}",
tokenName(token)));
}
}
/**
* Parses the select.
*/
private SelectQuery parseSelect()
throws SQLException
{
return parseSelect(new SelectQuery(_database, _sql));
}
/**
* Parses the select.
*/
private SelectQuery parseSelect(SelectQuery query)
throws SQLException
{
boolean distinct = false;
int token = scanToken();
if (token == DISTINCT)
distinct = true;
else
_token = token;
ArrayList resultItems = new ArrayList();
int startToken = scanToken();
String startLexeme = _lexeme;
int startOffset = _parseIndex;
while ((token = scanToken()) >= 0 && token != FROM) {
}
if (token != FROM)
throw error(L.l("expected FROM at `{0}'", tokenName(token)));
query.setParent(_query);
_query = query;
AndExpr oldAnd = _andExpr;
_andExpr = new AndExpr();
ArrayList fromItems = parseFromItems();
query.setFromItems(fromItems);
token = scanToken();
int tailToken = token;
int tailOffset = _parseIndex;
_token = startToken;
_parseIndex = startOffset;
_lexeme = startLexeme;
Expr expr = parseSelectExpr();
resultItems.add(expr);
while ((token = scanToken()) == ',') {
expr = parseSelectExpr();
resultItems.add(expr);
}
_token = tailToken;
_parseIndex = tailOffset;
token = scanToken();
if (token == WHERE)
_andExpr.add(parseExpr());
else
_token = token;
ParamExpr []params = _params.toArray(new ParamExpr[_params.size()]);
Expr whereExpr = _andExpr.getSingleExpr();
_andExpr = null;
query.setWhereExpr(whereExpr);
query.setParams(params);
for (int i = resultItems.size() - 1; i >= 0; i--) {
Expr subExpr = resultItems.get(i);
if (subExpr instanceof UnboundStarExpr) {
UnboundStarExpr unboundExpr = (UnboundStarExpr) subExpr;
ArrayList exprList = unboundExpr.expand(query.getFromItems());
resultItems.remove(i);
resultItems.addAll(i, exprList);
}
}
ArrayList groupItems = null;
token = scanToken();
if (token == GROUP) {
token = scanToken();
if (token != BY)
throw error(L.l("expected BY at `{0}'", tokenName(token)));
groupItems = parseGroup(query);
}
else
_token = token;
token = scanToken();
if (token == ORDER) {
token = scanToken();
if (token != BY)
throw error(L.l("expected BY at `{0}'", tokenName(token)));
Order order = parseOrder(query, resultItems);
}
else
_token = token;
Expr []resultArray = resultItems.toArray(new Expr[resultItems.size()]);
query.setResults(resultArray);
if (query.isGroup()) {
Expr []resultList = query.getResults();
bindGroup(query, groupItems);
for (int i = 0; i < resultList.length; i++) {
Expr subExpr = resultList[i];
if (! (subExpr instanceof GroupExpr)) {
resultList[i] = new GroupResultExpr(i, subExpr);
}
}
}
token = scanToken();
if (token == LIMIT) {
parseLimit(query);
}
else
_token = token;
if (query.getParent() == null
&& token >= 0 && token != LIMIT && token != OFFSET)
throw error(L.l("unexpected token at end '{0}'", tokenName(token)));
_query = query.getParent();
_andExpr = oldAnd;
return query;
}
private ArrayList parseFromItems()
throws SQLException
{
ArrayList fromItems = new ArrayList();
int token;
// XXX: somewhat hacked syntax
while ((token = scanToken()) == '(') {
}
_token = token;
FromItem fromItem = parseFromItem();
if (fromItem != null)
fromItems.add(fromItem);
int parenCount = 0;
while (true) {
token = scanToken();
boolean isNatural = false;
boolean isOuter = false;
boolean isLeft = true;
boolean isRight = true;
if (token == ',') {
fromItem = parseFromItem();
fromItems.add(fromItem);
continue;
}
else if (token == '(') {
parenCount++;
continue;
}
else if (token == ')') {
if (--parenCount < 0) {
_token = token;
break;
}
else
continue;
}
else if (token != IDENTIFIER) {
_token = token;
break;
}
else if ("join".equalsIgnoreCase(_lexeme)) {
}
else if ("inner".equalsIgnoreCase(_lexeme)) {
String join = parseIdentifier();
if (! "join".equalsIgnoreCase(join))
throw error(L.l("expected JOIN at '{0}'", join));
}
else if ("left".equalsIgnoreCase(_lexeme)) {
String name = parseIdentifier();
if ("outer".equalsIgnoreCase(name))
name = parseIdentifier();
if (! "join".equalsIgnoreCase(name))
throw error(L.l("expected JOIN at '{0}'", name));
isOuter = true;
}
else if ("right".equalsIgnoreCase(_lexeme)) {
String name = parseIdentifier();
if ("outer".equalsIgnoreCase(name))
name = parseIdentifier();
if (! "join".equalsIgnoreCase(name))
throw error(L.l("expected JOIN at '{0}'", name));
isRight = true;
isOuter = true;
throw error(L.l("right outer joins are not supported"));
}
else if ("natural".equalsIgnoreCase(_lexeme)) {
String name = parseIdentifier();
isNatural = true;
if ("left".equalsIgnoreCase(name)) {
name = parseIdentifier();
if ("outer".equalsIgnoreCase(name))
name = parseIdentifier();
isOuter = true;
}
else if ("right".equalsIgnoreCase(name)) {
name = parseIdentifier();
if ("outer".equalsIgnoreCase(name))
name = parseIdentifier();
isRight = true;
isOuter = true;
throw error(L.l("right outer joins are not supported"));
}
if (! "join".equalsIgnoreCase(name))
throw error(L.l("expected JOIN at '{0}'", name));
}
else {
_token = token;
break;
}
fromItem = parseFromItem();
fromItems.add(fromItem);
_query.setFromItems(fromItems);
token = scanToken();
if (token == IDENTIFIER && "on".equalsIgnoreCase(_lexeme)) {
Expr onExpr = parseExpr();
if (isOuter) {
FromItem leftItem = fromItems.get(fromItems.size() - 2);
FromItem rightItem = fromItems.get(fromItems.size() - 1);
onExpr = new LeftOuterJoinExpr(rightItem, onExpr);
rightItem.setDependTable(leftItem);
}
_andExpr.add(onExpr);
}
else
_token = token;
}
return fromItems;
}
/**
* Parses a select expression.
*/
private Expr parseSelectExpr()
throws SQLException
{
int token = scanToken();
if (token == '*')
return new UnboundStarExpr();
else {
_token = token;
return parseExpr();
}
}
/**
* Parses a from item
*/
private FromItem parseFromItem()
throws SQLException
{
String tableName = parseIdentifier();
if (tableName.equalsIgnoreCase("DUAL"))
return null;
Table table = _database.getTable(tableName);
if (table == null)
throw error(L.l("'{0}' is an unknown table. 'FROM table' requires an existing table.", tableName));
String name = table.getName();
int token = scanToken();
if (token == AS)
name = parseIdentifier();
else if (token == IDENTIFIER)
name = _lexeme;
else
_token = token;
return new FromItem(table, name);
}
/**
* Parses the ORDER BY
*/
private Order parseOrder(SelectQuery query,
ArrayList resultList)
throws SQLException
{
int token;
Order order = null;
do {
Expr expr = parseExpr();
expr = expr.bind(query);
token = scanToken();
boolean isAsc = true;
if (token == ASC)
isAsc = true;
else if (token == DESC)
isAsc = false;
else
_token = token;
int index;
for (index = 0; index < resultList.size(); index++) {
Expr resultExpr = resultList.get(index);
if (expr.equals(resultExpr))
break;
}
if (resultList.size() <= index) {
resultList.add(expr);
}
Order tailOrder = expr.createOrder(index);
tailOrder.setAscending(isAsc);
order = Order.append(order, tailOrder);
// ascList.add(isAsc ? Boolean.TRUE : Boolean.FALSE);
} while ((token = scanToken()) == ',');
query.setOrder(order);
_token = token;
return order;
}
/**
* Parses the GROUP BY
*/
private ArrayList parseGroup(SelectQuery query)
throws SQLException
{
query.setGroup(true);
int token;
ArrayList groupList = new ArrayList();
do {
groupList.add(parseExpr());
} while ((token = scanToken()) == ',');
_token = token;
return groupList;
}
/**
* Parses the GROUP BY
*/
private void bindGroup(SelectQuery query, ArrayList groupList)
throws SQLException
{
query.setGroup(true);
Expr []resultList = query.getResults();
for (int i = 0; i < groupList.size(); i++) {
Expr expr = groupList.get(i);
expr = expr.bind(query);
int index;
for (index = 0; index < resultList.length; index++) {
Expr resultExpr = resultList[index];
if (expr.equals(resultExpr)) {
resultList[index] = new GroupResultExpr(index, resultExpr);
break;
}
}
if (resultList.length <= index) {
throw error(L.l("GROUP BY field '{0}' must refer to a result field.",
expr));
}
query.setGroupResult(index);
}
}
/**
* Parses the LIMIT
*/
private void parseLimit(SelectQuery query)
throws SQLException
{
int token = scanToken();
if (token == INTEGER) {
query.setLimit(Integer.valueOf(_lexeme));
_token = scanToken();
}
else
throw error(L.l("LIMIT expected LIMIT int"));
}
/**
* Parses the create.
*/
private Query parseCreate()
throws SQLException
{
int token;
TableFactory factory = _database.createTableFactory();
if ((token = scanToken()) != TABLE)
throw error(L.l("expected TABLE at `{0}'", tokenName(token)));
if ((token = scanToken()) != IDENTIFIER)
throw error(L.l("expected identifier at `{0}'", tokenName(token)));
factory.startTable(_lexeme);
if ((token = scanToken()) != '(')
throw error(L.l("expected '(' at `{0}'", tokenName(token)));
do {
token = scanToken();
switch (token) {
case IDENTIFIER:
parseCreateColumn(factory, _lexeme);
break;
case UNIQUE:
token = scanToken();
if (token != KEY) {
_token = token;
}
factory.addUnique(parseColumnNames());
break;
case PRIMARY:
token = scanToken();
if (token != KEY)
throw error(L.l("expected 'key' at {0}", tokenName(token)));
factory.addPrimaryKey(parseColumnNames());
break;
case KEY:
String key = parseIdentifier();
parseColumnNames(); // factory.addPrimaryKey(parseColumnNames());
break;
case CHECK:
if ((token = scanToken()) != '(')
throw error(L.l("Expected '(' at '{0}'", tokenName(token)));
parseExpr();
if ((token = scanToken()) != ')')
throw error(L.l("Expected ')' at '{0}'", tokenName(token)));
break;
default:
throw error(L.l("unexpected token `{0}'", tokenName(token)));
}
token = scanToken();
} while (token == ',');
if (token != ')')
throw error(L.l("expected ')' at `{0}'", tokenName(token)));
return new CreateQuery(_database, _sql, factory);
}
/**
* Parses a column declaration.
*/
private void parseCreateColumn(TableFactory factory, String name)
throws SQLException
{
int token;
if ((token = scanToken()) != IDENTIFIER)
throw error(L.l("expected column type at {0}", tokenName(token)));
String type = _lexeme;
int length = -1;
int scale = -1;
if (type.equalsIgnoreCase("double")) {
if ((token = scanToken()) == IDENTIFIER) {
if (_lexeme.equalsIgnoreCase("precision")) {
}
else
throw error(L.l("unexpected double type at {0}", _lexeme));
}
else
_token = token;
}
if ((token = scanToken()) == '(') {
if ((token = scanToken()) != INTEGER)
throw error(L.l("expected column width at `{0}'", tokenName(token)));
length = Integer.parseInt(_lexeme);
if ((token = scanToken()) == ',') {
if ((token = scanToken()) != INTEGER)
throw error(L.l("expected column scale at `{0}'", tokenName(token)));
scale = Integer.parseInt(_lexeme);
token = scanToken();
}
if (token != ')')
throw error(L.l("expected ')' at '{0}'", tokenName(token)));
}
else
_token = token;
if (type.equalsIgnoreCase("varchar")) {
if (length < 0)
throw error(L.l("VARCHAR needs a defined length"));
factory.addVarchar(name, length);
}
else if (type.equalsIgnoreCase("char")) {
if (length < 0)
length = 1;
factory.addVarchar(name, length);
}
else if (type.equalsIgnoreCase("varbinary")) {
if (length < 0)
throw error(L.l("VARBINARY needs a defined length"));
factory.addVarbinary(name, length);
}
else if (type.equalsIgnoreCase("binary")) {
if (length < 0)
throw error(L.l("BINARY needs a defined length"));
factory.addBinary(name, length);
}
else if (type.equalsIgnoreCase("blob")) {
factory.addBlob(name);
}
else if (type.equalsIgnoreCase("tinytext")) {
factory.addTinytext(name);
}
else if (type.equalsIgnoreCase("mediumtext")) {
factory.addVarchar(name, 256);
}
else if (type.equalsIgnoreCase("longtext")) {
factory.addVarchar(name, 512);
}
else if (type.equalsIgnoreCase("smallint")
|| type.equalsIgnoreCase("tinyint")
|| type.equalsIgnoreCase("bit")) {
factory.addShort(name);
}
else if (type.equalsIgnoreCase("integer")
|| type.equalsIgnoreCase("int")
|| type.equalsIgnoreCase("mediumint")) {
factory.addInteger(name);
}
else if (type.equalsIgnoreCase("bigint")) {
factory.addLong(name);
}
else if (type.equalsIgnoreCase("double")
|| type.equalsIgnoreCase("float")
|| type.equalsIgnoreCase("real")) {
factory.addDouble(name);
}
else if (type.equalsIgnoreCase("datetime")
|| type.equalsIgnoreCase("timestamp")) {
factory.addDateTime(name);
}
else if (type.equalsIgnoreCase("text")
|| type.equalsIgnoreCase("clob")) {
factory.addVarchar(name, 255);
}
else if (type.equalsIgnoreCase("decimal")
|| type.equalsIgnoreCase("numeric")) {
factory.addNumeric(name, length, scale);
}
else if (type.equalsIgnoreCase("identity")) {
factory.addIdentity(name);
}
else
throw error(L.l("Unknown type {0}", type));
token = scanToken();
if (token == IDENTIFIER && _lexeme.equalsIgnoreCase("default")) {
Expr defaultExpr = parseExpr();
factory.setDefault(name, defaultExpr);
}
else
_token = token;
while (true) {
token = scanToken();
// XXX: stuff like NOT NULL
switch (token) {
case ')':
case ',':
_token = token;
return;
case UNIQUE:
factory.setUnique(name);
break;
case PRIMARY:
token = scanToken();
if (token != KEY)
throw error(L.l("expected key at {0}", tokenName(token)));
factory.setPrimaryKey(name);
break;
case CHECK:
if ((token = scanToken()) != '(')
throw error(L.l("Expected '(' at '{0}'", tokenName(token)));
parseExpr();
if ((token = scanToken()) != ')')
throw error(L.l("Expected ')' at '{0}'", tokenName(token)));
break;
case IDENTIFIER:
String id = _lexeme;
if (id.equalsIgnoreCase("references")) {
ArrayList foreignKey = new ArrayList();
foreignKey.add(name);
parseReferences(foreignKey);
}
else if (id.equalsIgnoreCase("default")) {
Expr expr = parseExpr();
}
else if (id.equalsIgnoreCase("auto_increment")) {
factory.setAutoIncrement(name, 1);
}
else if (id.equalsIgnoreCase("unsigned")) {
}
else if (id.equalsIgnoreCase("binary")) {
}
else
throw error(L.l("unexpected token '{0}'", tokenName(token)));
break;
case NULL:
break;
case NOT:
if ((token = scanToken()) == NULL)
factory.setNotNull(name);
else
throw error(L.l("unexpected token '{0}'", tokenName(token)));
break;
default:
throw error(L.l("unexpected token '{0}'", tokenName(token)));
}
}
}
/**
* Parses a key constraint declaration.
*/
private void parseKeyConstraint(TableFactory factory)
throws SQLException
{
String key = parseIdentifier();
int token = scanToken();
if (token == '(') {
parseIdentifier();
token = scanToken();
if (token != ')')
throw error("expected ')'");
}
else
_token = token;
}
/**
* Parses the references clause.
*/
public void parseReferences(ArrayList name)
throws SQLException
{
String foreignTable = parseIdentifier();
int token = scanToken();
ArrayList foreignColumns = new ArrayList();
if (token == '(') {
_token = token;
foreignColumns = parseColumnNames();
}
else
_token = token;
}
/**
* Parses a list of column names
*/
public ArrayList parseColumnNames()
throws SQLException
{
ArrayList columns = new ArrayList();
int token = scanToken();
if (token == '(') {
do {
columns.add(parseIdentifier());
token = scanToken();
} while (token == ',');
if (token != ')') {
throw error(L.l("expected ')' at '{0}'", tokenName(token)));
}
}
else if (token == IDENTIFIER) {
columns.add(_lexeme);
_token = token;
}
else
throw error(L.l("expected '(' at '{0}'", tokenName(token)));
return columns;
}
/**
* Parses the insert.
*/
private Query parseInsert()
throws SQLException
{
int token;
if ((token = scanToken()) != INTO)
throw error(L.l("expected INTO at `{0}'", tokenName(token)));
if ((token = scanToken()) != IDENTIFIER)
throw error(L.l("expected identifier at `{0}'", tokenName(token)));
Table table = _database.getTable(_lexeme);
if (table == null)
throw error(L.l("unknown table `{0}'", tokenName(token)));
FromItem fromItem = new FromItem(table, table.getName());
FromItem[] fromList = new FromItem[] { fromItem };
ArrayList columns = new ArrayList();
if ((token = scanToken()) == '(') {
do {
String columnName = parseIdentifier();
Column column = table.getColumn(columnName);
if (column == null)
throw new SQLException(L.l("`{0}' is not a valid column in {1}",
columnName, table.getName()));
columns.add(column);
} while ((token = scanToken()) == ',');
if (token != ')')
throw error(L.l("expected ')' at `{0}'", tokenName(token)));
token = scanToken();
}
else {
for (Column column : table.getColumns()) {
if (column.getTypeCode() != ColumnType.IDENTITY) {
columns.add(column);
}
}
}
if (token != VALUES)
throw error(L.l("expected VALUES at `{0}'", tokenName(token)));
if ((token = scanToken()) != '(')
throw error(L.l("expected '(' at `{0}'", tokenName(token)));
ArrayList values = new ArrayList();
InsertQuery query = new InsertQuery(_database, _sql, table, columns);
_query = query;
int i = 0;
do {
Expr expr = parseExpr();
expr = expr.bind(new TempQuery(fromList));
values.add(expr);
i++;
} while ((token = scanToken()) == ',');
if (token != ')')
throw error(L.l("expected ')' at {0}", tokenName(token)));
if (columns.size() != values.size())
throw error(L.l("number of columns does not match number of values"));
ParamExpr []params = _params.toArray(new ParamExpr[_params.size()]);
query.setParams(params);
query.setValues(values);
query.init();
return query;
}
/**
* Parses the delete.
*/
private Query parseDelete()
throws SQLException
{
int token;
if ((token = scanToken()) != FROM)
throw error(L.l("expected FROM at `{0}'", tokenName(token)));
if ((token = scanToken()) != IDENTIFIER)
throw error(L.l("expected identifier at `{0}'", tokenName(token)));
Table table = _database.getTable(_lexeme);
if (table == null)
throw error(L.l("unknown table `{0}'", tokenName(token)));
DeleteQuery query = new DeleteQuery(_database, _sql, table);
_query = query;
Expr whereExpr = null;
token = scanToken();
if (token == WHERE)
whereExpr = parseExpr();
else if (token >= 0)
throw error(L.l("expected WHERE at `{0}'", tokenName(token)));
ParamExpr []params = _params.toArray(new ParamExpr[_params.size()]);
query.setParams(params);
query.setWhereExpr(whereExpr);
return query;
}
/**
* Parses the delete.
*/
private Query parseValidate()
throws SQLException
{
int token;
if ((token = scanToken()) != IDENTIFIER)
throw error(L.l("expected identifier at '{0}'", tokenName(token)));
Table table = _database.getTable(_lexeme);
if (table == null)
throw error(L.l("unknown table '{0}'", tokenName(token)));
ValidateQuery query = new ValidateQuery(_database, _sql, table);
return query;
}
/**
* Parses the insert.
*/
private Query parseDrop()
throws SQLException
{
int token;
if ((token = scanToken()) != TABLE)
throw error(L.l("expected TABLE at `{0}'", tokenName(token)));
if ((token = scanToken()) != IDENTIFIER)
throw error(L.l("expected identifier at `{0}'", tokenName(token)));
String table = _lexeme;
if ((token = scanToken()) >= 0)
throw error(L.l("expected end of query at `{0}'", tokenName(token)));
return new DropQuery(_sql, _database, table);
}
/**
* Parses the select.
*/
private Query parseUpdate()
throws SQLException
{
int token;
if ((token = scanToken()) != IDENTIFIER)
throw error(L.l("expected identifier at `{0}'", tokenName(token)));
String name = _lexeme;
Table table = _database.getTable(name);
if (table == null)
throw error(L.l("`{0}' is an unknown table in INSERT.", name));
if ((token = scanToken()) != SET)
throw error(L.l("expected SET at {0}", tokenName(token)));
UpdateQuery query = new UpdateQuery(_database, _sql, table);
_query = query;
ArrayList setItemList = new ArrayList();
do {
SetItem item = parseSetItem(table);
setItemList.add(item);
} while ((token = scanToken()) == ',');
Expr whereExpr = null;
if (token == WHERE)
whereExpr = parseExpr();
SetItem []setItems = new SetItem[setItemList.size()];
setItemList.toArray(setItems);
ParamExpr []params = _params.toArray(new ParamExpr[_params.size()]);
query.setSetItems(setItems);
query.setParams(params);
query.setWhereExpr(whereExpr);
return query;
}
/*
private Query parseShow()
throws SQLException
{
int token;
if ((token = scanToken()) != IDENTIFIER)
throw error(L.l("expected identifier at `{0}'", tokenName(token)));
String name = _lexeme;
if (name.equalsIgnoreCase("tables"))
return new ShowTablesQuery();
else if (name.equalsIgnoreCase("databases"))
return new ShowDatabasesQuery();
else
throw error(L.l("`{0}' is an unknown type in SHOW.", name));
}
*/
/**
* Parses a set item.
*/
private SetItem parseSetItem(Table table)
throws SQLException
{
int token;
if ((token = scanToken()) != IDENTIFIER)
throw error(L.l("expected identifier at `{0}'", tokenName(token)));
Column column = table.getColumn(_lexeme);
if (column == null)
throw error(L.l("`{0}' is an unknown column in table {1}.",
_lexeme, table.getName()));
if ((token = scanToken()) != EQ)
throw error(L.l("expected `=' at {0}", tokenName(token)));
Expr expr = parseExpr();
return new SetItem(table, column, expr);
}
/**
* Parses an expression.
*/
private Expr parseExpr()
throws SQLException
{
int token = scanToken();
if (token == SELECT)
return parseSubSelect();
else {
_token = token;
return parseOrExpr();
}
}
/**
* Parses a sub-select expression.
*/
private Expr parseSubSelect()
throws SQLException
{
return parseSubSelect(new SelectQuery(_database, _sql));
}
/**
* Parses a sub-select expression.
*/
private Expr parseSubSelect(SelectQuery query)
throws SQLException
{
parseSelect(query);
SubSelectExpr expr = new SubSelectExpr(query);
query.setSubSelect(expr);
_andExpr.add(new SubSelectEvalExpr(expr));
return expr;
}
/**
* Parses an OR expression.
*/
private Expr parseOrExpr()
throws SQLException
{
Expr left = parseAndExpr();
while (true) {
int token = scanToken();
switch (token) {
case OR:
left = new OrExpr(left, parseAndExpr());
break;
default:
_token = token;
return left;
}
}
}
/**
* Parses an AND expression.
*/
private Expr parseAndExpr()
throws SQLException
{
AndExpr oldAndExpr = _andExpr;
AndExpr andExpr = new AndExpr();
_andExpr = andExpr;
andExpr.add(parseNotExpr());
while (true) {
int token = scanToken();
switch (token) {
case AND:
andExpr.add(parseNotExpr());
break;
default:
_token = token;
_andExpr = oldAndExpr;
return andExpr.getSingleExpr();
}
}
}
/**
* Parses a term.
*/
private Expr parseNotExpr()
throws SQLException
{
int token = scanToken();
switch (token) {
case NOT:
return new NotExpr(parseNotExpr());
default:
_token = token;
return parseCmpExpr();
}
}
/**
* Parses a CMP expression.
*/
private Expr parseCmpExpr()
throws SQLException
{
Expr left = parseConcatExpr();
int token = scanToken();
boolean isNot = false;
if (token == NOT) {
isNot = true;
token = scanToken();
if (token != BETWEEN && token != LIKE && token != IN) {
_token = token;
return left;
}
}
switch (token) {
case EQ:
return new EqExpr(left, parseConcatExpr());
case LT:
case LE:
case GT:
case GE:
case NE:
return new CmpExpr(left, parseConcatExpr(), token);
case BETWEEN:
{
Expr min = parseConcatExpr();
token = scanToken();
if (token != AND)
throw error(L.l("expected AND at '{0}'", tokenName(token)));
Expr max = parseConcatExpr();
return new BetweenExpr(left, min, max, isNot);
}
case IS:
{
token = scanToken();
isNot = false;
if (token == NOT) {
token = scanToken();
isNot = true;
}
if (token == NULL)
return new IsNullExpr(left, isNot);
else
throw error(L.l("expected NULL at '{0}'", tokenName(token)));
}
case LIKE:
{
token = scanToken();
if (token == STRING)
return new LikeExpr(left, _lexeme, isNot);
else
throw error(L.l("expected string at '{0}'", tokenName(token)));
}
case IN:
{
HashSet values = parseInValues();
return new InExpr(left, values, isNot);
}
default:
_token = token;
return left;
}
}
/**
* Parses the IN values.
*/
private HashSet parseInValues()
throws SQLException
{
int token = scanToken();
if (token != '(')
throw error(L.l("Expected '('"));
HashSet values = new HashSet();
while ((token = scanToken()) != ')') {
if (token == STRING) {
values.add(_lexeme);
}
else
throw error(L.l("expected STRING at {0}", tokenName(token)));
if ((token = scanToken()) != ',')
break;
}
if (token != ')')
throw error(L.l("expected ')' at {0}", tokenName(token)));
return values;
}
/**
* Parses a concat expression.
*/
private Expr parseConcatExpr()
throws SQLException
{
Expr left = parseAddExpr();
while (true) {
int token = scanToken();
switch (token) {
case CONCAT:
left = new ConcatExpr(left, parseAddExpr());
break;
default:
_token = token;
return left;
}
}
}
/**
* Parses a +/- expression.
*/
private Expr parseAddExpr()
throws SQLException
{
Expr left = parseMulExpr();
while (true) {
int token = scanToken();
switch (token) {
case '+':
case '-':
left = new BinaryExpr(left, parseMulExpr(), token);
break;
default:
_token = token;
return left;
}
}
}
/**
* Parses a mul/div expression
*/
private Expr parseMulExpr()
throws SQLException
{
Expr left = parseTerm();
while (true) {
int token = scanToken();
switch (token) {
case '*':
case '/':
case '%':
left = new BinaryExpr(left, parseTerm(), token);
break;
default:
_token = token;
return left;
}
}
}
/**
* Parses a term.
*/
private Expr parseTerm()
throws SQLException
{
int token = scanToken();
switch (token) {
case '+':
return parseTerm();
case '-':
return new UnaryExpr(parseTerm(), token);
case '(':
Expr expr = parseExpr();
int peekToken;
if ((peekToken = scanToken()) != ')')
throw error(L.l("expected ')' at {0}", tokenName(peekToken)));
return expr;
default:
_token = token;
return parseSimpleTerm();
}
}
/**
* Parses a simple term.
*/
private Expr parseSimpleTerm()
throws SQLException
{
int token = scanToken();
switch (token) {
case IDENTIFIER:
{
String name = _lexeme;
token = scanToken();
if (token == '.') {
token = scanToken();
if (token == IDENTIFIER) {
String column = _lexeme;
return _query.bind(name, column);
}
else if (token == '*') {
return new UnboundStarExpr(name);
}
else
throw error("expected IDENTIFIER");
}
else if (token == '(') {
FunExpr fun = null;
if (name.equalsIgnoreCase("max"))
fun = new MaxExpr();
else if (name.equalsIgnoreCase("min"))
fun = new MinExpr();
else if (name.equalsIgnoreCase("sum"))
fun = new SumExpr();
else if (name.equalsIgnoreCase("avg"))
fun = new AvgExpr();
else if (name.equalsIgnoreCase("count")) {
fun = new CountExpr();
token = scanToken();
if (token == '*') {
fun.addArg(new UnboundStarExpr());
}
else
_token = token;
}
else if (name.equalsIgnoreCase("exists")) {
token = scanToken();
if (token != SELECT)
throw error(L.l("exists requires SELECT at '{0}'",
tokenName(token)));
ExistsQuery query = new ExistsQuery(_database, _sql);
parseSelect(query);
ExistsExpr expr = new ExistsExpr(query);
query.setSubSelect(expr);
_andExpr.add(new ExistsEvalExpr(expr));
token = scanToken();
if (token != ')')
throw error(L.l("exists requires ')' at '{0}'",
tokenName(token)));
return expr;
}
else {
String funName = (Character.toUpperCase(name.charAt(0)) +
name.substring(1).toLowerCase(Locale.ENGLISH));
funName = "com.caucho.db.fun." + funName + "Expr";
try {
Class cl = Class.forName(funName);
fun = (FunExpr) cl.newInstance();
} catch (ClassNotFoundException e) {
log.finer(e.toString());
} catch (Throwable e) {
log.log(Level.FINER, e.toString(), e);
}
if (fun == null)
throw error(L.l("`{0}' is an unknown function.", name));
}
token = scanToken();
while (token > 0 && token != ')') {
_token = token;
Expr arg = parseExpr();
fun.addArg(arg);
token = scanToken();
if (token == ',')
token = scanToken();
}
return fun;
}
else {
_token = token;
return _query.bind(null, name);
}
}
case STRING:
return new StringExpr(_lexeme);
case DOUBLE:
case INTEGER:
case LONG:
return NumberExpr.create(_lexeme);
case NULL:
return new NullExpr();
case TRUE:
return BooleanLiteralExpr.create(true);
case FALSE:
return BooleanLiteralExpr.create(false);
case '?':
ParamExpr param = new ParamExpr(_params.size());
_params.add(param);
return param;
default:
throw error(L.l("unexpected term {0}", tokenName(token)));
}
}
/**
* Parses an identifier.
*/
private String parseIdentifier()
throws SQLException
{
int token = scanToken();
if (token != IDENTIFIER)
throw error(L.l("expected identifier at {0}", tokenName(token)));
return _lexeme;
}
/**
* Scan the next token. If the lexeme is a string, its string
* representation is in "lexeme".
*
* @return integer code for the token
*/
private int scanToken()
throws SQLException
{
if (_token > 0) {
int value = _token;
_token = -1;
return value;
}
int sign = 1;
int ch;
for (ch = read(); Character.isWhitespace((char) ch); ch = read()) {
}
switch (ch) {
case -1:
case '(':
case ')':
case '.':
case '*':
case '/':
case '%':
case ',':
case '?':
return ch;
case '+':
if ((ch = read()) >= '0' && ch <= '9')
break;
else {
unread(ch);
return '+';
}
case '-':
if ((ch = read()) >= '0' && ch <= '9') {
sign = -1;
break;
}
else {
unread(ch);
return '-';
}
case '=':
return EQ;
case '<':
if ((ch = read()) == '=')
return LE;
else if (ch == '>')
return NE;
else {
unread(ch);
return LT;
}
case '>':
if ((ch = read()) == '=')
return GE;
else {
unread(ch);
return GT;
}
case '|':
if ((ch = read()) == '|')
return CONCAT;
else {
throw error(L.l("'|' expected at {0}", charName(ch)));
}
// @@ is useless?
case '@':
if ((ch = read()) != '@')
throw error(L.l("`@' expected at {0}", charName(ch)));
return scanToken();
}
if (Character.isJavaIdentifierStart((char) ch)) {
CharBuffer cb = _cb;
cb.clear();
for (; ch > 0 && Character.isJavaIdentifierPart((char) ch); ch = read())
cb.append((char) ch);
unread(ch);
_lexeme = cb.toString();
String lower = _lexeme.toLowerCase(Locale.ENGLISH);
int token = _reserved.get(lower);
if (token > 0)
return token;
else
return IDENTIFIER;
}
else if (ch >= '0' && ch <= '9') {
CharBuffer cb = _cb;
cb.clear();
int type = INTEGER;
if (sign < 0)
cb.append('-');
for (; ch >= '0' && ch <= '9'; ch = read())
cb.append((char) ch);
if (ch == '.') {
type = DOUBLE;
cb.append('.');
for (ch = read(); ch >= '0' && ch <= '9'; ch = read())
cb.append((char) ch);
}
if (ch == 'e' || ch == 'E') {
type = DOUBLE;
cb.append('e');
if ((ch = read()) == '+' || ch == '-') {
cb.append((char) ch);
ch = read();
}
if (! (ch >= '0' && ch <= '9'))
throw error(L.l("exponent needs digits at {0}",
charName(ch)));
for (; ch >= '0' && ch <= '9'; ch = read())
cb.append((char) ch);
}
if (ch == 'F' || ch == 'D')
type = DOUBLE;
else if (ch == 'L') {
type = LONG;
}
else
unread(ch);
_lexeme = cb.toString();
return type;
}
else if (ch == '\'') {
CharBuffer cb = _cb;
cb.clear();
for (ch = read(); ch >= 0; ch = read()) {
if (ch == '\'') {
if ((ch = read()) == '\'')
cb.append('\'');
else {
unread(ch);
break;
}
}
else if (ch == '\\') {
ch = read();
if (ch >= 0)
cb.append(ch);
}
else
cb.append((char) ch);
}
_lexeme = cb.toString();
return STRING;
}
else if (ch == '#') {
// skip comment
while ((ch = read()) >= 0 && ch != '\n' && ch != '\r') {
}
// XXX: cleanup to avoid recursion
return scanToken();
}
throw error(L.l("unexpected char at {0} ({1})", "" + (char) ch,
String.valueOf(ch)));
}
/**
* Returns the next character.
*/
private int read()
{
if (_parseIndex < _sqlLength)
return _sqlChars[_parseIndex++];
else
return -1;
}
/**
* Unread the last character.
*/
private void unread(int ch)
{
if (ch >= 0)
_parseIndex--;
}
/**
* Returns the name for a character
*/
private String charName(int ch)
{
if (ch < 0)
return L.l("end of query");
else
return String.valueOf((char) ch);
}
/**
* Returns the name of a token
*/
private String tokenName(int token)
{
switch (token) {
case AS: return "AS";
case ARG: return "?";
case FROM: return "FROM";
case IN: return "IN";
case SELECT: return "SELECT";
case WHERE: return "WHERE";
case OR: return "OR";
case AND: return "AND";
case NOT: return "NOT";
case BETWEEN: return "BETWEEN";
case TRUE: return "TRUE";
case FALSE: return "FALSE";
case NULL: return "NULL";
case GROUP: return "GROUP";
case ORDER: return "ORDER";
case BY: return "BY";
case ASC: return "ASC";
case DESC: return "DESC";
case LIMIT: return "LIMIT";
case INSERT: return "INSERT";
case DELETE: return "DELETE";
case -1:
return L.l("end of query");
default:
if (token < 128)
return "'" + String.valueOf((char) token) + "' (" + token + ")";
else
return "'" + _lexeme + "' (" + token + ", '" + _lexeme.toLowerCase(Locale.ENGLISH) + "')";
}
}
private SQLException error(String msg)
{
return new SQLParseException(msg + "\n" + _sql);
}
static {
_reserved = new IntMap();
_reserved.put("as", AS);
_reserved.put("from", FROM);
_reserved.put("in", IN);
_reserved.put("select", SELECT);
_reserved.put("distinct", DISTINCT);
_reserved.put("where", WHERE);
_reserved.put("order", ORDER);
_reserved.put("group", GROUP);
_reserved.put("by", BY);
_reserved.put("asc", ASC);
_reserved.put("desc", DESC);
_reserved.put("limit", LIMIT);
_reserved.put("offset", OFFSET);
_reserved.put("or", OR);
_reserved.put("and", AND);
_reserved.put("not", NOT);
_reserved.put("between", BETWEEN);
_reserved.put("like", LIKE);
_reserved.put("escape", ESCAPE);
_reserved.put("is", IS);
_reserved.put("true", TRUE);
_reserved.put("false", FALSE);
_reserved.put("unknown", UNKNOWN);
_reserved.put("null", NULL);
_reserved.put("create", CREATE);
_reserved.put("table", TABLE);
_reserved.put("insert", INSERT);
_reserved.put("into", INTO);
_reserved.put("values", VALUES);
_reserved.put("drop", DROP);
_reserved.put("update", UPDATE);
_reserved.put("set", SET);
_reserved.put("delete", DELETE);
_reserved.put("validate", VALIDATE);
_reserved.put("constraint", CONSTRAINT);
_reserved.put("unique", UNIQUE);
_reserved.put("check", CHECK);
_reserved.put("primary", PRIMARY);
_reserved.put("key", KEY);
_reserved.put("foreign", FOREIGN);
}
}