/* Copyright (c) 1995-2000, The Hypersonic SQL Group. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the Hypersonic SQL Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP, * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This software consists of voluntary contributions made by many individuals * on behalf of the Hypersonic SQL Group. * * * For work added by the HSQL Development Group: * * Copyright (c) 2001-2008, The HSQL Development Group * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the HSQL Development Group nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.hsqldb; import java.util.Locale; import org.hsqldb.HsqlNameManager.HsqlName; import org.hsqldb.lib.ArrayUtil; import org.hsqldb.lib.HashMap; import org.hsqldb.lib.HashMappedList; import org.hsqldb.lib.HsqlArrayList; import org.hsqldb.lib.IntKeyHashMap; import org.hsqldb.lib.IntValueHashMap; import org.hsqldb.lib.Iterator; import org.hsqldb.lib.StringConverter; import org.hsqldb.store.ValuePool; import org.hsqldb.lib.HashSet; // fredt@users 20020130 - patch 497872 by Nitin Chauhan - reordering for speed // fredt@users 20020215 - patch 1.7.0 by fredt - support GROUP BY with more than one column // fredt@users 20020215 - patch 1.7.0 by fredt - SQL standard quoted identifiers // fredt@users 20020218 - patch 1.7.0 by fredt - DEFAULT keyword // fredt@users 20020221 - patch 513005 by sqlbob@users - SELECT INTO types // fredt@users 20020425 - patch 548182 by skitt@users - DEFAULT enhancement // thertz@users 20020320 - patch 473613 by thertz - outer join condition bug // fredt@users 20021229 - patch 1.7.2 by fredt - new solution for above // fredt@users 20020420 - patch 523880 by leptipre@users - VIEW support // fredt@users 20020525 - patch 559914 by fredt@users - SELECT INTO logging // tony_lai@users 20021020 - patch 1.7.2 - improved aggregates and HAVING // aggregate functions can now be used in expressions - HAVING supported // kloska@users 20021030 - patch 1.7.2 - ON UPDATE CASCADE // fredt@users 20021112 - patch 1.7.2 by Nitin Chauhan - use of switch // rewrite of the majority of multiple if(){}else{} chains with switch(){} // boucherb@users 20030705 - patch 1.7.2 - prepared statement support // fredt@users 20030819 - patch 1.7.2 - EXTRACT({YEAR | MONTH | DAY | HOUR | MINUTE | SECOND } FROM datetime) // fredt@users 20030820 - patch 1.7.2 - CHAR_LENGTH | CHARACTER_LENGTH | OCTET_LENGTH(string) // fredt@users 20030820 - patch 1.7.2 - POSITION(string IN string) // fredt@users 20030820 - patch 1.7.2 - SUBSTRING(string FROM pos [FOR length]) // fredt@users 20030820 - patch 1.7.2 - TRIM({LEADING | TRAILING | BOTH} [<character>] FROM <string expression>) // fredt@users 20030820 - patch 1.7.2 - CASE [expr] WHEN ... THEN ... [ELSE ...] END and its variants // fredt@users 20030820 - patch 1.7.2 - NULLIF(expr,expr) // fredt@users 20030820 - patch 1.7.2 - COALESCE(expr,expr,...) // fredt@users 20031012 - patch 1.7.2 - improved scoping for column names in all areas // boucherb@users 200403xx - patch 1.7.2 - added support for prepared SELECT INTO // boucherb@users 200403xx - doc 1.7.2 - some // thomasm@users 20041001 - patch 1.7.3 - BOOLEAN undefined handling // fredt@users 20050220 - patch 1.8.0 - CAST with precision / scale /* todo: fredt - implement remaining numeric value functions (SQL92 6.6) * * EXTRACT({TIMEZONE_HOUR | TIMEZONE_MINUTE} FROM {datetime | interval}) */ /** * Responsible for parsing non-DDL statements. * * Extensively rewritten and extended in successive versions of HSQLDB. * * @author Thomas Mueller (Hypersonic SQL Group) * @version 1.8.0 * @since Hypersonic SQL */ class Parser { private Database database; private Tokenizer tokenizer; private Session session; private String sSchema; private String sTable; private String sToken; private boolean wasQuoted; private Object oData; private int iType; private int iToken; private boolean compilingView; // private int subQueryLevel; private HsqlArrayList subQueryList = new HsqlArrayList(); /** * Constructs a new Parser object with the given context. * * @param db the Database instance against which to resolve named * database object references * @param t the token source from which to parse commands * @param session the connected context */ Parser(Session session, Database db, Tokenizer t) { database = db; tokenizer = t; this.session = session; } /** * sets a flag indicating the parser is used for compiling a view */ void setCompilingView() { compilingView = true; } /** * determines whether the parser is used for compiling a view */ boolean isCompilingView() { return compilingView; } /** * Resets this parse context with the given SQL character sequence. * * Internal structures are reset as though a new parser were created * with the given sql and the originally specified database and session * * @param a new SQL character sequence to replace the current one */ void reset(String sql) { sTable = null; sToken = null; oData = null; tokenizer.reset(sql); subQueryList.clear(); subQueryLevel = 0; parameters.clear(); } /** * Tests whether the parsing session has the given write access on the * given Table object. <p> * * @param table the Table object to check * @param userRight the numeric code of the right to check * @throws HsqlException if the session user does not have the right * or the given Table object is simply not writable (e.g. is a * non-updateable View) */ void checkTableWriteAccess(Table table, int userRight) throws HsqlException { // session level user rights session.checkReadWrite(); // object level user rights session.check(table.getName(), userRight); // object type if (table.isView()) { throw Trace.error(Trace.NOT_A_TABLE, table.getName().name); } // object readonly table.checkDataReadOnly(); } /** * Parses a comma-separated, right-bracket terminated list of column * names. <p> * * @param db the Database instance whose name manager is to provide the * resulting HsqlName objects, when the full argument is true * @param t the tokenizer representing the character sequence to be parsed * @param full if true, generate a list of HsqlNames, else a list of * String objects */ static HsqlArrayList getColumnNames(Database db, Table table, Tokenizer t, boolean full) throws HsqlException { HsqlArrayList columns = new HsqlArrayList(); while (true) { if (full) { String token = t.getSimpleName(); boolean quoted = t.wasQuotedIdentifier(); HsqlName name = db.nameManager.newHsqlName(token, quoted); columns.add(name); } else { columns.add(t.getName()); if (t.wasLongName() && !t.getLongNameFirst().equals( table.getName().name)) { throw (Trace.error(Trace.TABLE_NOT_FOUND, t.getLongNameFirst())); } } String token = t.getSimpleToken(); if (token.equals(Token.T_COMMA)) { continue; } if (token.equals(Token.T_CLOSEBRACKET)) { break; } t.throwUnexpected(); } return columns; } /** * The SubQuery objects are added to the end of subquery list. * * When parsing the SELECT for a view, optional HsqlName[] array is used * for view column aliases. * */ SubQuery parseSubquery(int brackets, HsqlName[] colNames, boolean resolveAll, int predicateType) throws HsqlException { SubQuery sq; sq = new SubQuery(); subQueryLevel++; boolean canHaveOrder = predicateType == Expression.VIEW || predicateType == Expression.SELECT; boolean canHaveLimit = predicateType == Expression.SELECT || predicateType == Expression.VIEW || predicateType == Expression.QUERY; boolean limitWithOrder = predicateType == Expression.IN || predicateType == Expression.ALL || predicateType == Expression.ANY; Select s = parseSelect(brackets, canHaveOrder, canHaveLimit, limitWithOrder, true); sq.level = subQueryLevel; subQueryLevel--; boolean isResolved = s.resolveAll(session, resolveAll); sq.select = s; sq.isResolved = isResolved; // it's not a problem that this table has not a unique name HsqlName sqtablename = database.nameManager.newHsqlName("SYSTEM_SUBQUERY", false); sqtablename.schema = database.schemaManager.SYSTEM_SCHEMA_HSQLNAME; Table table = new Table(database, sqtablename, Table.SYSTEM_SUBQUERY); if (colNames != null) { if (colNames.length != s.iResultLen) { throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH); } for (int i = 0; i < s.iResultLen; i++) { HsqlName name = colNames[i]; s.exprColumns[i].setAlias(name.name, name.isNameQuoted); } } else { for (int i = 0; i < s.iResultLen; i++) { String colname = s.exprColumns[i].getAlias(); if (colname == null || colname.length() == 0) { // fredt - this does not guarantee the uniqueness of column // names but addColumns() will throw if names are not unique. colname = "COL_" + String.valueOf(i + 1); s.exprColumns[i].setAlias(colname, false); } } } table.addColumns(s); boolean uniqueValues = predicateType == Expression.EXISTS || predicateType == Expression.IN || predicateType == Expression.ALL || predicateType == Expression.ANY; int[] pcol = null; if (uniqueValues) { pcol = new int[s.iResultLen]; ArrayUtil.fillSequence(pcol); } table.createPrimaryKey(pcol); sq.table = table; sq.uniqueRows = uniqueValues; subQueryList.add(sq); return sq; } SubQuery getViewSubquery(View v) { SubQuery sq = v.viewSubQuery; for (int i = 0; i < v.viewSubqueries.length; i++) { subQueryList.add(v.viewSubqueries[i]); } return sq; } /** * Constructs and returns a Select object. * * @param canHaveOrder whether the SELECT being parsed can have an ORDER BY * @param canHaveLimit whether LIMIT without ORDER BY is allowed * @param limitWithOrder whether LIMIT is allowed only with ORDER BY * @param isMain whether the SELECT being parsed is the first * select statement in the set * @return a new Select object * @throws HsqlException if a parsing error occurs */ Select parseSelect(int brackets, boolean canHaveOrder, boolean canHaveLimit, boolean limitWithOrder, boolean isMain) throws HsqlException { Select select = new Select(); String token = tokenizer.getString(); if (canHaveLimit || limitWithOrder) { if (tokenizer.wasThis(Token.T_LIMIT) || tokenizer.wasThis(Token.T_TOP)) { parseLimit(token, select, false); token = tokenizer.getString(); } } if (tokenizer.wasThis(Token.T_DISTINCT)) { select.isDistinctSelect = true; } else if (tokenizer.wasThis(Token.T_ALL)) {} else { tokenizer.back(); } // parse column list HsqlArrayList vcolumn = new HsqlArrayList(); do { int expPos = tokenizer.getPosition(); Expression e = parseExpression(); if (isCompilingView()) { if (e.getType() == Expression.ASTERISK) { if (select.asteriskPositions == null) { select.asteriskPositions = new IntKeyHashMap(); } // remember the position of the asterisk. For the moment, just // remember the expression, so it can later be found and replaced // with the concrete column list select.asteriskPositions.put(expPos, e); } } token = tokenizer.getString(); if (tokenizer.wasThis(Token.T_AS)) { e.setAlias(tokenizer.getSimpleName(), tokenizer.wasQuotedIdentifier()); token = tokenizer.getString(); } else if (tokenizer.wasSimpleName()) { e.setAlias(token, tokenizer.wasQuotedIdentifier()); token = tokenizer.getString(); } vcolumn.add(e); } while (tokenizer.wasThis(Token.T_COMMA)); if (token.equals(Token.T_INTO)) { boolean getname = true; token = tokenizer.getString(); select.intoType = database.getDefaultTableType(); if (tokenizer.wasSimpleToken()) { switch (Token.get(token)) { case Token.CACHED : select.intoType = Table.CACHED_TABLE; break; case Token.TEMP : select.intoType = Table.TEMP_TABLE; break; case Token.TEXT : select.intoType = Table.TEXT_TABLE; break; case Token.MEMORY : select.intoType = Table.MEMORY_TABLE; break; default : getname = false; break; } if (getname) { token = tokenizer.getName(); } } if (!tokenizer.wasName()) { tokenizer.throwUnexpected(); } select.sIntoTable = database.nameManager.newHsqlName(token, tokenizer.wasQuotedIdentifier()); select.sIntoTable.schema = session.getSchemaHsqlName(tokenizer.getLongNameFirst()); token = tokenizer.getString(); } tokenizer.matchThis(Token.T_FROM); Expression condition = null; // parse table list HsqlArrayList vfilter = new HsqlArrayList(); vfilter.add(parseTableFilter(false)); while (true) { token = tokenizer.getString(); boolean cross = false; if (tokenizer.wasThis(Token.T_INNER)) { tokenizer.getThis(Token.T_JOIN); token = Token.T_JOIN; } else if (tokenizer.wasThis(Token.T_CROSS)) { tokenizer.getThis(Token.T_JOIN); token = Token.T_JOIN; cross = true; } if (token.equals(Token.T_LEFT) && !tokenizer.wasQuotedIdentifier()) { tokenizer.isGetThis(Token.T_OUTER); tokenizer.getThis(Token.T_JOIN); TableFilter tf = parseTableFilter(true); vfilter.add(tf); tokenizer.getThis(Token.T_ON); Expression newcondition = parseExpression(); newcondition.checkTables(vfilter); condition = addJoinCondition(condition, newcondition, tf, true); // MarcH HuugO RIGHT JOIN SUPPORT } else if (token.equals(Token.T_RIGHT) && !tokenizer.wasQuotedIdentifier()) { tokenizer.isGetThis(Token.T_OUTER); tokenizer.getThis(Token.T_JOIN); // this object is not an outerjoin, the next object is an outerjoin TableFilter tf = parseTableFilter(false); // insert new condition as first element in a new vfilter (nvfilter), copy the content of vfilter and rename nvfilter back to vfilter. HsqlArrayList nvfilter = new HsqlArrayList(); nvfilter.add(tf); nvfilter.addAll(vfilter); vfilter = nvfilter; // set isOuterJoin correct ((TableFilter) vfilter.get(1)).isOuterJoin = true; tokenizer.getThis(Token.T_ON); Expression newcondition = parseExpression(); newcondition.checkTables(vfilter); condition = addJoinCondition(condition, newcondition, ((TableFilter) vfilter.get(1)), true); } else if (tokenizer.wasThis(Token.T_JOIN)) { vfilter.add(parseTableFilter(false)); if (!cross) { tokenizer.getThis(Token.T_ON); Expression newcondition = parseExpression(); newcondition.checkTables(vfilter); condition = addJoinCondition(condition, newcondition, null, false); } } else if (tokenizer.wasThis(Token.T_COMMA)) { vfilter.add(parseTableFilter(false)); } else { tokenizer.back(); break; } } resolveSelectTableFilter(select, vcolumn, vfilter); // where token = tokenizer.getString(); if (tokenizer.wasThis(Token.T_WHERE)) { Expression newcondition = parseExpression(); condition = addCondition(condition, newcondition); token = tokenizer.getString(); } select.queryCondition = condition; // group by if (tokenizer.wasThis(Token.T_GROUP)) { tokenizer.getThis(Token.T_BY); int len = 0; do { Expression e = parseExpression(); vcolumn.add(e); token = tokenizer.getString(); len++; } while (tokenizer.wasThis(Token.T_COMMA)); select.iGroupLen = len; } // having if (tokenizer.wasThis(Token.T_HAVING)) { select.iHavingLen = 1; select.havingCondition = parseExpression(); token = tokenizer.getString(); vcolumn.add(select.havingCondition); } if (isMain || limitWithOrder) { if (tokenizer.wasThis(Token.T_ORDER)) { tokenizer.getThis(Token.T_BY); parseOrderBy(select, vcolumn); token = tokenizer.getString(); } if (tokenizer.wasThis(Token.T_LIMIT)) { parseLimit(token, select, true); token = tokenizer.getString(); } } boolean closebrackets = false; if (brackets > 0 && token.equals(Token.T_CLOSEBRACKET)) { closebrackets = true; brackets -= parseCloseBrackets(brackets - 1) + 1; token = tokenizer.getString(); } select.unionDepth = brackets; // checks for ORDER and LIMIT if (!(isMain || closebrackets)) { limitWithOrder = false; } boolean hasOrder = select.iOrderLen != 0; boolean hasLimit = select.limitCondition != null; if (limitWithOrder) { if (hasLimit && !hasOrder) { throw Trace.error(Trace.ORDER_LIMIT_REQUIRED); } } else { if (hasOrder && !canHaveOrder) { throw Trace.error(Trace.INVALID_ORDER_BY); } if (hasLimit && !canHaveLimit) { throw Trace.error(Trace.INVALID_LIMIT); } } int unionType = parseUnion(token); if (unionType != Select.NOUNION) { boolean openbracket = false; select.unionType = unionType; if (tokenizer.isGetThis(Token.T_OPENBRACKET)) { openbracket = true; brackets += parseOpenBrackets() + 1; } tokenizer.getThis(Token.T_SELECT); // accept ORDRY BY with LIMIT when in brackets select.unionSelect = parseSelect(brackets, false, false, openbracket, false); token = tokenizer.getString(); } if (isMain && (canHaveOrder || limitWithOrder) && select.iOrderLen == 0) { if (tokenizer.wasThis(Token.T_ORDER)) { tokenizer.getThis(Token.T_BY); parseOrderBy(select, vcolumn); token = tokenizer.getString(); select.sortUnion = true; } if (tokenizer.wasThis(Token.T_LIMIT)) { parseLimit(token, select, true); token = tokenizer.getString(); } } tokenizer.back(); if (isMain) { select.prepareUnions(); } int len = vcolumn.size(); select.exprColumns = new Expression[len]; vcolumn.toArray(select.exprColumns); return select; } /** * Parses the given token and any further tokens in tokenizer to return * any UNION or other set operation ID. */ int parseUnion(String token) throws HsqlException { int unionType = Select.NOUNION; if (tokenizer.wasSimpleToken()) { switch (Token.get(token)) { case Token.UNION : token = tokenizer.getSimpleToken(); if (token.equals(Token.T_ALL)) { unionType = Select.UNIONALL; } else if (token.equals(Token.T_DISTINCT)) { unionType = Select.UNION; } else { unionType = Select.UNION; tokenizer.back(); } break; case Token.INTERSECT : tokenizer.isGetThis(Token.T_DISTINCT); unionType = Select.INTERSECT; break; case Token.EXCEPT : case Token.MINUS : tokenizer.isGetThis(Token.T_DISTINCT); unionType = Select.EXCEPT; break; default : break; } } return unionType; } // fredt@users 20011010 - patch 471710 by fredt - LIMIT rewritten // SELECT LIMIT n m DISTINCT ... queries and error message // "SELECT LIMIT n m ..." creates the result set for the SELECT statement then // discards the first n rows and returns m rows of the remaining result set // "SELECT LIMIT 0 m" is equivalent to "SELECT TOP m" or "SELECT FIRST m" // in other RDBMS's // "SELECT LIMIT n 0" discards the first n rows and returns the remaining rows // fredt@users 20020225 - patch 456679 by hiep256 - TOP keyword private void parseLimit(String token, Select select, boolean isEnd) throws HsqlException { if (select.limitCondition != null) { return; } Expression e1 = null; Expression e2; boolean islimit = false; if (isEnd) { if (token.equals(Token.T_LIMIT)) { islimit = true; read(); e2 = readTerm(); if (sToken.equals(Token.T_OFFSET)) { read(); e1 = readTerm(); } tokenizer.back(); } else { return; } } else if (token.equals(Token.T_LIMIT)) { read(); e1 = readTerm(); e2 = readTerm(); islimit = true; tokenizer.back(); } else if (token.equals(Token.T_TOP)) { read(); e2 = readTerm(); tokenizer.back(); } else { return; } if (e1 == null) { e1 = new Expression(Types.INTEGER, ValuePool.getInt(0)); } if (e1.isParam() || (e1.getType() == Expression.VALUE && e1.getDataType() == Types.INTEGER && e1.getValue(null) != null && ((Integer) e1.getValue(null)).intValue() >= 0)) { if (e2.isParam() || (e2.getType() == Expression.VALUE && e2.getDataType() == Types.INTEGER && e2.getValue(null) != null && ((Integer) e2.getValue(null)).intValue() >= 0)) { // necessary for params e1.setDataType(Types.INTEGER); e2.setDataType(Types.INTEGER); select.limitCondition = new Expression(Expression.LIMIT, e1, e2); return; } } int messageid = islimit ? Trace.INVALID_LIMIT_EXPRESSION : Trace.INVALID_TOP_EXPRESSION; throw Trace.error(Trace.WRONG_DATA_TYPE, messageid); } private void parseOrderBy(Select select, HsqlArrayList vcolumn) throws HsqlException { String token; int len = 0; do { Expression e = parseExpression(); e = resolveOrderByExpression(e, select, vcolumn); token = tokenizer.getString(); if (token.equals(Token.T_DESC)) { e.setDescending(); token = tokenizer.getString(); } else if (token.equals(Token.T_ASC)) { token = tokenizer.getString(); } vcolumn.add(e); len++; } while (token.equals(Token.T_COMMA)); tokenizer.back(); select.iOrderLen = len; } private void resolveSelectTableFilter(Select select, HsqlArrayList vcolumn, HsqlArrayList vfilter) throws HsqlException { int colcount; TableFilter[] filters = new TableFilter[vfilter.size()]; vfilter.toArray(filters); select.tFilter = filters; // expand [table.]* columns colcount = vcolumn.size(); for (int pos = 0; pos < colcount; ) { Expression e = (Expression) (vcolumn.get(pos)); if (e.getType() == Expression.ASTERISK) { vcolumn.remove(pos); colcount = vcolumn.size(); String tablename = e.getTableName(); int oldPos = pos; if (tablename == null) { for (int i = 0; i < filters.length; i++) { pos = addFilterColumns(filters[i], vcolumn, pos); colcount = vcolumn.size(); } } else { TableFilter f = e.findTableFilter(filters); if (f == null) { throw Trace.error(Trace.TABLE_NOT_FOUND, tablename); } pos = addFilterColumns(f, vcolumn, pos); colcount = vcolumn.size(); } if (isCompilingView()) { // find this expression's position in the Select's asterisk list boolean foundAsteriskPos = false; Iterator expSearch = select.asteriskPositions.keySet().iterator(); while (expSearch.hasNext()) { int expPos = expSearch.nextInt(); if (e == select.asteriskPositions.get(expPos)) { // compile the complete column list which later is to replace the asterisk StringBuffer completeColList = new StringBuffer(); for (int col = oldPos; col < pos; ++col) { Expression resolvedColExpr = (Expression) (vcolumn.get(col)); completeColList.append( resolvedColExpr.getColumnDDL()); if (col < pos - 1) { completeColList.append(","); } } select.asteriskPositions.put( expPos, completeColList.toString()); foundAsteriskPos = true; break; } } Trace.doAssert(foundAsteriskPos); } } else { if (e.getFilter() == null) { for (int i = 0; i < filters.length; i++) { e.resolveTables(filters[i]); } } pos++; } } for (int i = 0; i < colcount; i++) { Expression e = (Expression) (vcolumn.get(i)); e.resolveTypes(session); } select.iResultLen = colcount; } /** * Add all columns of a table filter to list of columns */ int addFilterColumns(TableFilter filter, HsqlArrayList columnList, int position) throws HsqlException { Table table = filter.getTable(); int count = table.getColumnCount(); for (int i = 0; i < count; i++) { Expression e = new Expression(filter, table.getColumn(i)); if (isCompilingView()) { e.resolveTables(filter); } columnList.add(position++, e); } return position; } /** * Resolves an ORDER BY Expression, returning the column Expression object * to which it refers if it is an alias or column index. <p> * * If select is a SET QUERY, then only column indexes or names in the first * query are allowed. * * @param e search column expression * @param vcolumn list of columns * @param union is select a union * @return new or the same expression * @throws HsqlException if an ambiguous reference to an alias or * non-integer column index is encountered */ private static Expression resolveOrderByExpression(Expression e, Select select, HsqlArrayList vcolumn) throws HsqlException { int visiblecols = select.iResultLen; boolean union = select.unionSelect != null; if (e.getType() == Expression.VALUE) { return resolveOrderByColumnIndex(e, vcolumn, visiblecols); } if (e.getType() != Expression.COLUMN) { if (union) { throw Trace.error(Trace.INVALID_ORDER_BY); } return e; } String ecolname = e.getColumnName(); String etablename = e.getTableName(); for (int i = 0, size = visiblecols; i < size; i++) { Expression colexpr = (Expression) vcolumn.get(i); String colalias = colexpr.getDefinedAlias(); String colname = colexpr.getColumnName(); String tablename = colexpr.getTableName(); String filtername = colexpr.getFilterTableName(); if ((ecolname.equals(colalias) || ecolname.equals(colname)) && (etablename == null || etablename.equals(tablename) || etablename.equals(filtername))) { colexpr.joinedTableColumnIndex = i; return colexpr; } } if (union) { throw Trace.error(Trace.INVALID_ORDER_BY, ecolname); } return e; } private static Expression resolveOrderByColumnIndex(Expression e, HsqlArrayList vcolumn, int visiblecols) throws HsqlException { // order by 1,2,3 if (e.getDataType() == Types.INTEGER) { int i = ((Integer) e.getValue(null)).intValue(); if (0 < i && i <= visiblecols) { Expression colexpr = (Expression) vcolumn.get(i - 1); colexpr.joinedTableColumnIndex = i - 1; return colexpr; } } throw Trace.error(Trace.INVALID_ORDER_BY); } private TableFilter parseSimpleTableFilter(int type) throws HsqlException { String alias = null; String token = tokenizer.getName(); String schema = session.getSchemaName(tokenizer.getLongNameFirst()); Table table = database.schemaManager.getTable(session, token, schema); checkTableWriteAccess(table, type); // token = tokenizer.getString(); if (token.equals(Token.T_AS)) { alias = tokenizer.getSimpleName(); } else if (tokenizer.wasSimpleName()) { alias = token; } else { tokenizer.back(); } return new TableFilter(table, alias, null, false); } /** * Retrieves a TableFilter object newly constructed from the current * parse context. <p> * * @param outerjoin if the filter is to back an outer join * @return a newly constructed TableFilter object * @throws HsqlException if a parsing error occurs */ private TableFilter parseTableFilter(boolean outerjoin) throws HsqlException { Table t = null; SubQuery sq = null; String sAlias = null; HashMappedList columnList = null; if (tokenizer.isGetThis(Token.T_OPENBRACKET)) { int brackets = parseOpenBrackets(); tokenizer.getThis(Token.T_SELECT); // fredt - not correlated - a joined subquery table must resolve fully sq = parseSubquery(brackets, null, true, Expression.QUERY); tokenizer.getThis(Token.T_CLOSEBRACKET); t = sq.table; } else { String token = tokenizer.getName(); String schema = session.getSchemaName(tokenizer.getLongNameFirst()); t = database.schemaManager.getTable(session, token, schema); session.check(t.getName(), UserManager.SELECT); if (t.isView()) { sq = getViewSubquery((View) t); sq.select = ((View) t).viewSelect; t = sq.table; sAlias = token; } } // fredt - we removed LEFT from the list of reserved words in Tokenizer // to allow LEFT() to work. Thus wasName() will return true for LEFT // and we check separately for this token String token = tokenizer.getString(); if (tokenizer.wasLongName()) { tokenizer.throwUnexpected(); } if ((token.equals(Token.T_LEFT) || token.equals(Token.T_RIGHT)) && !tokenizer.wasQuotedIdentifier()) { tokenizer.back(); } else if (token.equals(Token.T_AS) && !tokenizer.wasQuotedIdentifier()) { sAlias = tokenizer.getSimpleName(); if (tokenizer.isGetThis(Token.T_OPENBRACKET)) { tokenizer.back(); columnList = parseColumnList(); } } else if (tokenizer.wasSimpleName()) { sAlias = token; if (tokenizer.isGetThis(Token.T_OPENBRACKET)) { tokenizer.back(); columnList = parseColumnList(); } } else { tokenizer.back(); } if (columnList != null && t.getColumnCount() != columnList.size()) { throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH); } return new TableFilter(t, sAlias, columnList, outerjoin); } /** * Add a condition from the WHERE clause. * * @param e1 * @param e2 * @return */ private static Expression addCondition(Expression e1, Expression e2) { if (e1 == null) { return e2; } else if (e2 == null) { return e1; } else { return new Expression(Expression.AND, e1, e2); } } /** * Conjuntively adds a condition from the JOIN table ON clause. * * @param e1 an existing condition with which e2 is to be combined * in order to form a new conjunction * @param e2 the new condition * @param tf the table filter that should become e2's join * table filter * @param outer true if join is outer * @throws HsqlException if e2 responds that it cannot participate * in the join * @return a new Expression object; the conjunction of e1 and e2 */ private static Expression addJoinCondition(Expression e1, Expression e2, TableFilter tf, boolean outer) throws HsqlException { if (!e2.setForJoin(tf, outer)) { throw Trace.error(Trace.OUTER_JOIN_CONDITION); } return addCondition(e1, e2); } /** * Method declaration * * @return the Expression resulting from the parse * @throws HsqlException */ Expression parseExpression() throws HsqlException { read(); Expression r = readOr(); tokenizer.back(); return r; } private Expression readAggregate() throws HsqlException { boolean distinct = false; boolean all = false; int type = iToken; read(); String token = tokenizer.getString(); if (token.equals(Token.T_DISTINCT)) { distinct = true; } else if (token.equals(Token.T_ALL)) { all = true; } else { tokenizer.back(); } readThis(Expression.OPEN); Expression s = readOr(); readThis(Expression.CLOSE); if ((all || distinct) && (type == Expression.STDDEV_POP || type == Expression.STDDEV_SAMP || type == Expression.VAR_POP || type == Expression.VAR_SAMP)) { throw Trace.error(Trace.INVALID_FUNCTION_ARGUMENT); } Expression aggregateExp = new Expression(type, s, null); aggregateExp.setDistinctAggregate(distinct); return aggregateExp; } /** * Method declaration * * @return a disjuntion, possibly degenerate * @throws HsqlException */ private Expression readOr() throws HsqlException { Expression r = readAnd(); while (iToken == Expression.OR) { int type = iToken; Expression a = r; read(); r = new Expression(type, a, readAnd()); } return r; } /** * Method declaration * * @return a conjunction, possibly degenerate * @throws HsqlException */ private Expression readAnd() throws HsqlException { Expression r = readCondition(); while (iToken == Expression.AND) { int type = iToken; Expression a = r; read(); r = new Expression(type, a, readCondition()); } return r; } /** * Method declaration * * @return a predicate, possibly composite * @throws HsqlException */ private Expression readCondition() throws HsqlException { switch (iToken) { case Expression.NOT : { int type = iToken; read(); return new Expression(type, readCondition(), null); } case Expression.EXISTS : { int type = iToken; read(); readThis(Expression.OPEN); int brackets = 0; if (iToken == Expression.OPEN) { brackets += parseOpenBrackets() + 1; read(); } Trace.check(iToken == Expression.SELECT, Trace.UNEXPECTED_TOKEN); SubQuery sq = parseSubquery(brackets, null, false, Expression.EXISTS); Expression s = new Expression(sq); read(); readThis(Expression.CLOSE); return new Expression(type, s, null); } default : { Expression a = readConcat(); if (iToken == Expression.IS) { read(); boolean not; if (iToken == Expression.NOT) { not = true; read(); } else { not = false; } Trace.check(iToken == Expression.VALUE && oData == null, Trace.UNEXPECTED_TOKEN); read(); // TODO: the TableFilter needs a right hand side to avoid null pointer exceptions... a = new Expression(Expression.IS_NULL, a, new Expression(Types.NULL, null)); if (not) { a = new Expression(Expression.NOT, a, null); } return a; } boolean not = false; if (iToken == Expression.NOT) { not = true; read(); } switch (iToken) { case Expression.LIKE : { a = parseLikePredicate(a); break; } case Expression.BETWEEN : { a = parseBetweenPredicate(a); break; } case Expression.IN : { a = this.parseInPredicate(a); break; } default : { Trace.check(!not, Trace.UNEXPECTED_TOKEN); if (Expression.isCompare(iToken)) { int type = iToken; read(); return new Expression(type, a, readConcat()); } return a; } } if (not) { a = new Expression(Expression.NOT, a, null); } return a; } } } private Expression parseLikePredicate(Expression a) throws HsqlException { read(); Expression b = readConcat(); Character escape = null; if (sToken.equals(Token.T_ESCAPE)) { read(); Expression c = readTerm(); Trace.check(c.getType() == Expression.VALUE, Trace.INVALID_ESCAPE); String s = (String) c.getValue(session, Types.VARCHAR); // boucherb@users 2003-09-25 // CHECKME: // Assert s.length() == 1 for xxxchar comparisons? // TODO: // SQL200n says binary escape can be 1 or more octets. // Maybe we need to retain s and check this in // Expression.resolve()? if (s == null || s.length() < 1) { throw Trace.error(Trace.INVALID_ESCAPE, s); } escape = new Character(s.charAt(0)); } boolean hasCollation = database.collation.name != null; a = new Expression(a, b, escape, hasCollation); return a; } private Expression parseBetweenPredicate(Expression a) throws HsqlException { read(); Expression l = new Expression(Expression.BIGGER_EQUAL, a, readConcat()); readThis(Expression.AND); Expression h = new Expression(Expression.SMALLER_EQUAL, a, readConcat()); if (l.getArg().isParam() && l.getArg2().isParam()) { throw Trace.error(Trace.UNRESOLVED_PARAMETER_TYPE, Trace.Parser_ambiguous_between1); } if (h.getArg().isParam() && h.getArg2().isParam()) { throw Trace.error(Trace.UNRESOLVED_PARAMETER_TYPE, Trace.Parser_ambiguous_between1); } return new Expression(Expression.AND, l, h); } private Expression parseInPredicate(Expression a) throws HsqlException { int type = iToken; read(); readThis(Expression.OPEN); Expression b = null; int brackets = 0; if (iToken == Expression.OPEN) { brackets += parseOpenBrackets() + 1; read(); } if (iToken == Expression.SELECT) { SubQuery sq = parseSubquery(brackets, null, false, Expression.IN); // until we support rows in IN predicates Trace.check(sq.select.iResultLen == 1, Trace.SINGLE_COLUMN_EXPECTED); b = new Expression(sq); read(); } else { tokenizer.back(); HsqlArrayList v = new HsqlArrayList(); while (true) { Expression value = parseExpression(); if (value.exprType == Expression.VALUE && value.valueData == null && !value.isParam()) { throw Trace.error(Trace.NULL_IN_VALUE_LIST); } v.add(value); read(); if (iToken != Expression.COMMA) { break; } } Expression[] valueList; valueList = (Expression[]) v.toArray(new Expression[v.size()]); b = new Expression(valueList); } readThis(Expression.CLOSE); return new Expression(type, a, b); } private Expression parseAllAnyPredicate() throws HsqlException { int type = iToken; read(); readThis(Expression.OPEN); Expression b = null; int brackets = 0; if (iToken == Expression.OPEN) { brackets += parseOpenBrackets() + 1; read(); } if (iToken != Expression.SELECT) { throw Trace.error(Trace.INVALID_IDENTIFIER); } SubQuery sq = parseSubquery(brackets, null, false, type); Select select = sq.select; // until we support rows Trace.check(sq.select.iResultLen == 1, Trace.SINGLE_COLUMN_EXPECTED); b = new Expression(sq); read(); readThis(Expression.CLOSE); return new Expression(type, b, null); } /** * Method declaration * * @param type * @throws HsqlException */ private void readThis(int type) throws HsqlException { Trace.check(iToken == type, Trace.UNEXPECTED_TOKEN); read(); } /** * Method declaration * * @return a concatenation, possibly degenerate * @throws HsqlException */ private Expression readConcat() throws HsqlException { Expression r = readSum(); while (iToken == Expression.CONCAT) { int type = Expression.CONCAT; Expression a = r; read(); r = new Expression(type, a, readSum()); } return r; } static HashMap simpleFunctions = new HashMap(); static { simpleFunctions.put(Token.T_CURRENT_DATE, "org.hsqldb.Library.curdate"); simpleFunctions.put(Token.T_CURRENT_TIME, "org.hsqldb.Library.curtime"); simpleFunctions.put(Token.T_CURRENT_TIMESTAMP, "org.hsqldb.Library.now"); simpleFunctions.put(Token.T_CURRENT_USER, "org.hsqldb.Library.user"); simpleFunctions.put(Token.T_SYSDATE, "org.hsqldb.Library.curdate"); simpleFunctions.put(Token.T_NOW, "org.hsqldb.Library.now"); simpleFunctions.put(Token.T_TODAY, "org.hsqldb.Library.curdate"); } /** * Method declaration * * @return a summation, possibly degenerate * @throws HsqlException */ private Expression readSum() throws HsqlException { Expression r = readFactor(); while (true) { int type; if (iToken == Expression.PLUS) { type = Expression.ADD; } else if (iToken == Expression.NEGATE) { type = Expression.SUBTRACT; } else { break; } Expression a = r; read(); r = new Expression(type, a, readFactor()); } return r; } /** * Method declaration * * @return a product, possibly degenerate * @throws HsqlException */ private Expression readFactor() throws HsqlException { Expression r = readTerm(); while (iToken == Expression.MULTIPLY || iToken == Expression.DIVIDE) { int type = iToken; Expression a = r; read(); r = new Expression(type, a, readTerm()); } return r; } /** * Method declaration * * @return a term, possibly composite * @throws HsqlException */ private Expression readTerm() throws HsqlException { Expression r = null; switch (iToken) { case Expression.COLUMN : { r = readColumnExpression(); break; } case Expression.NEGATE : { int type = iToken; read(); r = new Expression(type, readTerm(), null); Trace.check(!r.getArg().isParam(), Trace.Expression_resolveTypes1); break; } case Expression.PLUS : { read(); r = readTerm(); Trace.check(!r.isParam(), Trace.UNRESOLVED_PARAMETER_TYPE, Trace.getMessage(Trace.Expression_resolveTypes1)); break; } case Expression.OPEN : { read(); r = readOr(); if (iToken != Expression.CLOSE) { throw Trace.error(Trace.UNEXPECTED_TOKEN, sToken); } read(); break; } case Expression.VALUE : { r = new Expression(iType, oData); read(); break; } case Expression.PARAM : { r = new Expression(Types.NULL, null, true); parameters.add(r); read(); break; } case Expression.SELECT : { SubQuery sq = parseSubquery(0, null, false, Expression.SELECT); r = new Expression(sq); read(); break; } case Expression.ANY : case Expression.ALL : { r = parseAllAnyPredicate(); // read(); break; } case Expression.MULTIPLY : { r = new Expression(sSchema, sTable, (String) null); read(); break; } case Expression.CASEWHEN : return readCaseWhenExpression(); case Expression.CASE : return readCaseExpression(); case Expression.NULLIF : return readNullIfExpression(); case Expression.COALESCE : case Expression.IFNULL : return readCoalesceExpression(); case Expression.SEQUENCE : return readSequenceExpression(); case Expression.CAST : case Expression.CONVERT : return readCastExpression(); case Expression.EXTRACT : return readExtractExpression(); case Expression.TRIM : return readTrimExpression(); case Expression.POSITION : return readPositionExpression(); case Expression.SUBSTRING : return readSubstringExpression(); default : if (Expression.isAggregate(iToken)) { return readAggregate(); } else { throw Trace.error(Trace.UNEXPECTED_TOKEN, sToken); } } return r; } /** * reads a CASE .. WHEN expression */ Expression readCaseExpression() throws HsqlException { int type = Expression.CASEWHEN; Expression r = null; Expression predicand = null; read(); if (iToken != Expression.WHEN) { predicand = readOr(); } Expression leaf = null; while (true) { Expression casewhen = parseCaseWhen(predicand); if (r == null) { r = casewhen; } else { leaf.setRightExpression(casewhen); } leaf = casewhen.getRightExpression(); if (iToken != Expression.WHEN) { break; } } if (iToken == Expression.ELSE) { readThis(Expression.ELSE); Expression elsexpr = readOr(); leaf.setRightExpression(elsexpr); } readThis(Expression.ENDWHEN); return r; } /** * Reads part of a CASE .. WHEN expression */ private Expression parseCaseWhen(Expression r) throws HsqlException { readThis(Expression.WHEN); Expression condition; if (r == null) { condition = readOr(); } else { condition = new Expression(Expression.EQUAL, r, readOr()); } readThis(Expression.THEN); Expression current = readOr(); Expression alternatives = new Expression(Expression.ALTERNATIVE, current, new Expression(Types.NULL, null)); Expression casewhen = new Expression(Expression.CASEWHEN, condition, alternatives); return casewhen; } /** * reads a CASEWHEN expression */ private Expression readCaseWhenExpression() throws HsqlException { int type = iToken; Expression r = null; read(); readThis(Expression.OPEN); r = readOr(); readThis(Expression.COMMA); Expression thenelse = readOr(); readThis(Expression.COMMA); // thenelse part is never evaluated; only init thenelse = new Expression(Expression.ALTERNATIVE, thenelse, readOr()); r = new Expression(type, r, thenelse); readThis(Expression.CLOSE); return r; } /** * Reads a CAST or CONVERT expression */ private Expression readCastExpression() throws HsqlException { boolean isConvert = iToken == Expression.CONVERT; read(); readThis(Expression.OPEN); Expression r = readOr(); if (isConvert) { readThis(Expression.COMMA); } else { readThis(Expression.AS); } int typeNr = Types.getTypeNr(sToken); int length = 0; int scale = 0; boolean hasLength = false; if (Types.acceptsPrecisionCreateParam(typeNr) && tokenizer.isGetThis(Token.T_OPENBRACKET)) { length = tokenizer.getInt(); hasLength = true; if (Types.acceptsScaleCreateParam(typeNr) && tokenizer.isGetThis(Token.T_COMMA)) { scale = tokenizer.getInt(); } tokenizer.getThis(Token.T_CLOSEBRACKET); } if (typeNr == Types.FLOAT && length > 53) { throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE); } if (typeNr == Types.TIMESTAMP) { if (!hasLength) { length = 6; } else if (length != 0 && length != 6) { throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE); } } if (r.isParam()) { r.setDataType(typeNr); } r = new Expression(r, typeNr, length, scale); read(); readThis(Expression.CLOSE); return r; } /** * reads a Column or Function expression */ private Expression readColumnExpression() throws HsqlException { String name = sToken; Expression r = new Expression(sTable, name, wasQuoted); read(); if (iToken == Expression.OPEN) { String javaName = database.getJavaName(name); Function f = new Function(name, javaName, false); session.check(javaName); int len = f.getArgCount(); int i = 0; read(); if (iToken != Expression.CLOSE) { while (true) { f.setArgument(i++, readOr()); if (iToken != Expression.COMMA) { break; } read(); } } readThis(Expression.CLOSE); r = new Expression(f); } else { String javaName = (String) simpleFunctions.get(name); if (javaName != null) { Function f = new Function(name, javaName, true); r = new Expression(f); } } return r; } /** * reads a CONCAT expression */ private Expression readConcatExpression() throws HsqlException { int type = iToken; read(); readThis(Expression.OPEN); Expression r = readOr(); readThis(Expression.COMMA); r = new Expression(type, r, readOr()); readThis(Expression.CLOSE); return r; } /** * Reads a NULLIF expression */ private Expression readNullIfExpression() throws HsqlException { // turn into a CASEWHEN read(); readThis(Expression.OPEN); Expression r = readOr(); readThis(Expression.COMMA); Expression thenelse = new Expression(Expression.ALTERNATIVE, new Expression(Types.NULL, null), r); r = new Expression(Expression.EQUAL, r, readOr()); r = new Expression(Expression.CASEWHEN, r, thenelse); readThis(Expression.CLOSE); return r; } /** * Reads a COALESE or IFNULL expression */ private Expression readCoalesceExpression() throws HsqlException { Expression r = null; // turn into a CASEWHEN read(); readThis(Expression.OPEN); Expression leaf = null; while (true) { Expression current = readOr(); if (leaf != null && iToken == Expression.CLOSE) { readThis(Expression.CLOSE); leaf.setLeftExpression(current); break; } Expression condition = new Expression(Expression.IS_NULL, current, null); Expression alternatives = new Expression(Expression.ALTERNATIVE, new Expression(Types.NULL, null), current); Expression casewhen = new Expression(Expression.CASEWHEN, condition, alternatives); if (r == null) { r = casewhen; } else { leaf.setLeftExpression(casewhen); } leaf = alternatives; readThis(Expression.COMMA); } return r; } /** * Reads an EXTRACT expression */ private Expression readExtractExpression() throws HsqlException { read(); readThis(Expression.OPEN); String name = sToken; // must be an accepted identifier if (!Expression.SQL_EXTRACT_FIELD_NAMES.contains(name)) { throw Trace.error(Trace.UNEXPECTED_TOKEN, sToken); } readToken(); readThis(Expression.FROM); // the name argument is DAY, MONTH etc. - OK for now for CHECK constraints Function f = new Function(name, database.getJavaName(name), false); f.setArgument(0, readOr()); readThis(Expression.CLOSE); return new Expression(f); } /** * Reads a POSITION expression */ private Expression readPositionExpression() throws HsqlException { read(); readThis(Expression.OPEN); Function f = new Function(Token.T_POSITION, "org.hsqldb.Library.position", false); f.setArgument(0, readTerm()); readThis(Expression.IN); f.setArgument(1, readOr()); readThis(Expression.CLOSE); return new Expression(f); } /** * Reads a SUBSTRING expression */ private Expression readSubstringExpression() throws HsqlException { boolean commas = false; read(); readThis(Expression.OPEN); // OK for now for CHECK search conditions Function f = new Function(Token.T_SUBSTRING, "org.hsqldb.Library.substring", false); f.setArgument(0, readTerm()); if (iToken == Expression.FROM) { readThis(Expression.FROM); } else { readThis(Expression.COMMA); commas = true; } f.setArgument(1, readOr()); Expression count = null; if (!commas && iToken == Expression.FOR) { readThis(Expression.FOR); count = readTerm(); } else if (commas && iToken == Expression.COMMA) { readThis(Expression.COMMA); count = readTerm(); } f.setArgument(2, count); readThis(Expression.CLOSE); return new Expression(f); } private Expression readSequenceExpression() throws HsqlException { tokenizer.getThis(Token.T_VALUE); tokenizer.getThis(Token.T_FOR); String name = tokenizer.getName(); String schemaname = tokenizer.getLongNameFirst(); schemaname = session.getSchemaName(schemaname); // Read next because Tokenizer.back() will run after this. // (This is because usually when reading expressions, you need to // get the following token to know whether you have finished. tokenizer.getString(); NumberSequence sequence = database.schemaManager.getSequence(name, schemaname); return new Expression(sequence); } /** * Reads a TRIM expression */ private Expression readTrimExpression() throws HsqlException { read(); readThis(Expression.OPEN); String type = sToken; if (Expression.SQL_TRIM_SPECIFICATION.contains(type)) { read(); } else { type = Token.T_BOTH; } String trimstr; if (sToken.length() == 1) { trimstr = sToken; read(); } else { trimstr = " "; } readThis(Expression.FROM); Expression trim = new Expression(Types.CHAR, trimstr); Expression leading; Expression trailing; if (type.equals(Token.T_LEADING)) { leading = new Expression(true); trailing = new Expression(false); } else if (type.equals(Token.T_TRAILING)) { leading = new Expression(false); trailing = new Expression(true); } else { leading = trailing = new Expression(true); } // name argument is OK for now for CHECK constraints Function f = new Function(Token.T_TRIM, "org.hsqldb.Library.trim", false); f.setArgument(0, readOr()); f.setArgument(1, trim); f.setArgument(2, leading); f.setArgument(3, trailing); readThis(Expression.CLOSE); return new Expression(f); } /** * Reads a DEFAULT clause expression. */ Expression readDefaultClause(int dataType) throws HsqlException { Expression r = null; read(); switch (iToken) { case Expression.COLUMN : { String name = sToken; String javaName = (String) simpleFunctions.get(name); if (javaName != null) { Function f = new Function(name, javaName, true); return new Expression(f); } break; } case Expression.NEGATE : { int exprType = iToken; read(); if (iToken == Expression.VALUE) { oData = Column.convertObject(oData, dataType); return new Expression(exprType, new Expression(dataType, oData), null); } break; } case Expression.VALUE : { String name = sToken.toUpperCase(Locale.ENGLISH); String javaName = (String) simpleFunctions.get(name); if (Types.isDatetimeType(dataType) && javaName != null) { Function f = new Function(name, javaName, true); return new Expression(f); } oData = Column.convertObject(oData, dataType); return new Expression(dataType, oData); } } throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE, sToken); } /** * Method declaration * * @throws HsqlException */ private void read() throws HsqlException { sToken = tokenizer.getString(); wasQuoted = tokenizer.wasQuotedIdentifier(); if (tokenizer.wasValue()) { iToken = Expression.VALUE; oData = tokenizer.getAsValue(); iType = tokenizer.getType(); } else if (tokenizer.wasSimpleName()) { iToken = Expression.COLUMN; sTable = null; } else if (tokenizer.wasLongName()) { sSchema = tokenizer.getLongNamePre(); sTable = tokenizer.getLongNameFirst(); if (sToken.equals(Token.T_MULTIPLY)) { iToken = Expression.MULTIPLY; } else { iToken = Expression.COLUMN; } } else if (tokenizer.wasParameter()) { iToken = Expression.PARAM; } else if (sToken.length() == 0) { iToken = Expression.END; } else { iToken = tokenSet.get(sToken, -1); if (iToken == -1) { iToken = Expression.END; } switch (iToken) { case Expression.COMMA : case Expression.EQUAL : case Expression.NOT_EQUAL : case Expression.SMALLER : case Expression.BIGGER : case Expression.SMALLER_EQUAL : case Expression.BIGGER_EQUAL : case Expression.AND : case Expression.OR : case Expression.NOT : case Expression.ALL : case Expression.ANY : case Expression.IN : case Expression.EXISTS : case Expression.BETWEEN : case Expression.PLUS : case Expression.NEGATE : case Expression.DIVIDE : case Expression.CONCAT : case Expression.OPEN : case Expression.CLOSE : case Expression.SELECT : case Expression.LIKE : case Expression.COUNT : case Expression.SUM : case Expression.MIN : case Expression.MAX : case Expression.AVG : case Expression.EVERY : case Expression.SOME : case Expression.STDDEV_POP : case Expression.STDDEV_SAMP : case Expression.VAR_POP : case Expression.VAR_SAMP : case Expression.CONVERT : case Expression.CAST : case Expression.SEQUENCE : case Expression.IFNULL : case Expression.COALESCE : case Expression.NULLIF : case Expression.CASE : case Expression.WHEN : case Expression.THEN : case Expression.ELSE : case Expression.ENDWHEN : case Expression.CASEWHEN : case Expression.EXTRACT : case Expression.POSITION : case Expression.SUBSTRING : case Expression.FROM : case Expression.FOR : case Expression.END : case Expression.PARAM : case Expression.TRIM : case Expression.LEADING : case Expression.TRAILING : case Expression.BOTH : case Expression.AS : case Expression.IS : case Expression.DISTINCT : break; // nothing else required, iToken initialized properly case Expression.MULTIPLY : sTable = null; // in case of ASTERIX break; default : iToken = Expression.END; } } } /** * A workaround for parsing EXTRACT clause elements such as MONTH, DAY * and YEAR, without having to make each of them SQL KEYWORDS in Tokenizer. * * @throws HsqlException if a tokenization error occurs */ private void readToken() throws HsqlException { sToken = tokenizer.getString(); iToken = tokenSet.get(sToken, -1); } private static IntValueHashMap tokenSet = new IntValueHashMap(37); static { tokenSet.put(Token.T_COMMA, Expression.COMMA); tokenSet.put(Token.T_EQUALS, Expression.EQUAL); tokenSet.put("!=", Expression.NOT_EQUAL); tokenSet.put("<>", Expression.NOT_EQUAL); tokenSet.put("<", Expression.SMALLER); tokenSet.put(">", Expression.BIGGER); tokenSet.put("<=", Expression.SMALLER_EQUAL); tokenSet.put(">=", Expression.BIGGER_EQUAL); tokenSet.put(Token.T_AND, Expression.AND); tokenSet.put(Token.T_NOT, Expression.NOT); tokenSet.put(Token.T_OR, Expression.OR); tokenSet.put(Token.T_ALL, Expression.ALL); tokenSet.put(Token.T_ANY, Expression.ANY); tokenSet.put(Token.T_IN, Expression.IN); tokenSet.put(Token.T_EXISTS, Expression.EXISTS); tokenSet.put(Token.T_BETWEEN, Expression.BETWEEN); tokenSet.put(Token.T_PLUS, Expression.PLUS); tokenSet.put("-", Expression.NEGATE); tokenSet.put(Token.T_MULTIPLY, Expression.MULTIPLY); tokenSet.put("/", Expression.DIVIDE); tokenSet.put("||", Expression.CONCAT); tokenSet.put(Token.T_OPENBRACKET, Expression.OPEN); tokenSet.put(Token.T_CLOSEBRACKET, Expression.CLOSE); tokenSet.put(Token.T_SELECT, Expression.SELECT); tokenSet.put(Token.T_LIKE, Expression.LIKE); tokenSet.put(Token.T_COUNT, Expression.COUNT); tokenSet.put(Token.T_SUM, Expression.SUM); tokenSet.put(Token.T_MIN, Expression.MIN); tokenSet.put(Token.T_MAX, Expression.MAX); tokenSet.put(Token.T_AVG, Expression.AVG); tokenSet.put(Token.T_EVERY, Expression.EVERY); tokenSet.put(Token.T_SOME, Expression.SOME); tokenSet.put(Token.T_STDDEV_POP, Expression.STDDEV_POP); tokenSet.put(Token.T_STDDEV_SAMP, Expression.STDDEV_SAMP); tokenSet.put(Token.T_VAR_POP, Expression.VAR_POP); tokenSet.put(Token.T_VAR_SAMP, Expression.VAR_SAMP); tokenSet.put(Token.T_IFNULL, Expression.IFNULL); tokenSet.put(Token.T_NVL, Expression.IFNULL); tokenSet.put(Token.T_NULLIF, Expression.NULLIF); tokenSet.put(Token.T_CONVERT, Expression.CONVERT); tokenSet.put(Token.T_CAST, Expression.CAST); tokenSet.put(Token.T_NEXT, Expression.SEQUENCE); tokenSet.put(Token.T_CASE, Expression.CASE); tokenSet.put(Token.T_WHEN, Expression.WHEN); tokenSet.put(Token.T_THEN, Expression.THEN); tokenSet.put(Token.T_ELSE, Expression.ELSE); tokenSet.put(Token.T_END, Expression.ENDWHEN); tokenSet.put(Token.T_CASEWHEN, Expression.CASEWHEN); tokenSet.put(Token.T_COALESCE, Expression.COALESCE); tokenSet.put(Token.T_EXTRACT, Expression.EXTRACT); tokenSet.put(Token.T_POSITION, Expression.POSITION); tokenSet.put(Token.T_FROM, Expression.FROM); tokenSet.put(Token.T_TRIM, Expression.TRIM); tokenSet.put(Token.T_SUBSTRING, Expression.SUBSTRING); tokenSet.put(Token.T_FOR, Expression.FOR); tokenSet.put(Token.T_AS, Expression.AS); tokenSet.put(Token.T_IS, Expression.IS); tokenSet.put(Token.T_QUESTION, Expression.PARAM); } // boucherb@users 20030411 - patch 1.7.2 - for prepared statements // --------------------------------------------------------------- HsqlArrayList parameters = new HsqlArrayList(); private static final Expression[] noParameters = new Expression[0]; private static final SubQuery[] noSubqueries = new SubQuery[0]; /** * Destructive get method */ Expression[] getParameters() { Expression[] result = parameters.size() == 0 ? noParameters : (Expression[]) parameters.toArray( new Expression[parameters.size()]); parameters.clear(); return result; } void clearParameters() { parameters.clear(); } // fredt - new implementation of subquery list /** * Sets the subqueries as belonging to the View being constructed */ void setAsView(View view) { for (int i = 0; i < subQueryList.size(); i++) { SubQuery sq = (SubQuery) subQueryList.get(i); if (sq.view == null) { sq.view = view; } } } /** * Return the list of subqueries as an array sorted according to the order * of materialization, then clear the internal subquery list */ SubQuery[] getSortedSubqueries() { if (subQueryList.size() == 0) { return noSubqueries; } subQueryList.sort((SubQuery) subQueryList.get(0)); SubQuery[] subqueries = new SubQuery[subQueryList.size()]; subQueryList.toArray(subqueries); subQueryList.clear(); return subqueries; } /** * Retrieves a CALL-type CompiledStatement from this parse context. */ CompiledStatement compileCallStatement() throws HsqlException { clearParameters(); Expression expression = parseExpression(); CompiledStatement cs = new CompiledStatement(session, database, session.currentSchema, expression, getSortedSubqueries(), getParameters()); return cs; } /** * Retrieves a DELETE-type CompiledStatement from this parse context. */ CompiledStatement compileDeleteStatement() throws HsqlException { String token; Expression condition = null; TableFilter tableFilter; clearParameters(); tokenizer.getThis(Token.T_FROM); tableFilter = parseSimpleTableFilter(UserManager.DELETE); token = tokenizer.getString(); if (token.equals(Token.T_WHERE)) { condition = parseExpression(); } else { tokenizer.back(); } CompiledStatement cs = new CompiledStatement(session, database, session.currentSchema, tableFilter, condition, getSortedSubqueries(), getParameters()); return cs; } private void getInsertColumnValueExpressions(Table t, Expression[] acve, int len) throws HsqlException { tokenizer.getThis(Token.T_OPENBRACKET); for (int i = 0; i < len; i++) { Expression columnValExpression = parseExpression(); columnValExpression.resolveTables(null); columnValExpression.resolveTypes(session); acve[i] = columnValExpression; String token = tokenizer.getSimpleToken(); if (token.equals(Token.T_COMMA)) { continue; } if (token.equals(Token.T_CLOSEBRACKET)) { if (i == len - 1) { return; } else { break; } } tokenizer.throwUnexpected(); } throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH); } /** * Retrieves an INSERT_XXX-type CompiledStatement from this parse context. */ CompiledStatement compileInsertStatement() throws HsqlException { clearParameters(); tokenizer.getThis(Token.T_INTO); HsqlArrayList columnNames; boolean[] columnCheckList; int[] columnMap; int len; String token = tokenizer.getName(); String schema = session.getSchemaName(tokenizer.getLongNameFirst()); Table table = database.schemaManager.getTable(session, token, schema); checkTableWriteAccess(table, UserManager.INSERT); columnNames = null; columnCheckList = null; columnMap = table.getColumnMap(); len = table.getColumnCount(); int brackets = parseOpenBrackets(); token = tokenizer.getString(); if (brackets == 1 && !tokenizer.wasThis(Token.T_SELECT)) { brackets = 0; tokenizer.back(); columnNames = getColumnNames(database, table, tokenizer, false); if (columnNames.size() > len) { throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH); } len = columnNames.size(); columnCheckList = table.getNewColumnCheckList(); columnMap = new int[len]; for (int i = 0; i < len; i++) { int ci = table.getColumnNr((String) columnNames.get(i)); columnMap[i] = ci; columnCheckList[ci] = true; } token = tokenizer.getSimpleToken(); } else if (!tokenizer.wasSimpleToken()) { tokenizer.throwUnexpected(); } int command = Token.get(token); switch (command) { case Token.VALUES : { Expression[] acve = new Expression[len]; getInsertColumnValueExpressions(table, acve, len); CompiledStatement cs = new CompiledStatement(session.currentSchema, table, columnMap, acve, columnCheckList, getSortedSubqueries(), getParameters()); return cs; } case Token.OPENBRACKET : { brackets = parseOpenBrackets() + 1; tokenizer.getThis(Token.T_SELECT); } case Token.SELECT : { // accept ORDER BY or ORDRY BY with LIMIT Select select = parseSelect(brackets, true, false, true, true); if (len != select.iResultLen) { throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH); } CompiledStatement cs = new CompiledStatement(session, database, session.currentSchema, table, columnMap, columnCheckList, select, getSortedSubqueries(), getParameters()); return cs; } default : { throw Trace.error(Trace.UNEXPECTED_TOKEN, token); } } } /** * Retrieves a SELECT-type CompiledStatement from this parse context. */ CompiledStatement compileSelectStatement(int brackets) throws HsqlException { clearParameters(); Select select = parseSelect(brackets, true, true, false, true); if (select.sIntoTable != null) { String name = select.sIntoTable.name; String schema = select.sIntoTable.schema.name; if (database.schemaManager.findUserTable(session, name, schema) != null) { throw Trace.error(Trace.TABLE_ALREADY_EXISTS, name); } } CompiledStatement cs = new CompiledStatement(session, database, session.currentSchema, select, getSortedSubqueries(), getParameters()); return cs; } /** * Retrieves an UPDATE-type CompiledStatement from this parse context. */ CompiledStatement compileUpdateStatement() throws HsqlException { String token; Table table; int[] colList; Expression[] exprList; int len; Expression cve; Expression condition; clearParameters(); TableFilter tableFilter = parseSimpleTableFilter(UserManager.UPDATE); table = tableFilter.filterTable; tokenizer.getThis(Token.T_SET); colList = table.getNewColumnMap(); exprList = new Expression[colList.length]; len = 0; token = null; do { int ci = table.getColumnNr(tokenizer.getName()); String tablename = tokenizer.getLongNameFirst(); if (tablename != null && !tableFilter.getName().equals(tablename)) { throw Trace.error(Trace.TABLE_NOT_FOUND); } tokenizer.getThis(Token.T_EQUALS); cve = parseExpression(); if (len == colList.length) { // too many (repeat) assignments throw Trace.error(Trace.COLUMN_COUNT_DOES_NOT_MATCH); } colList[len] = ci; exprList[len] = cve; token = tokenizer.getSimpleToken(); len++; } while (token.equals(Token.T_COMMA)); condition = null; if (token.equals(Token.T_WHERE)) { condition = parseExpression(); } else { tokenizer.back(); } colList = (int[]) ArrayUtil.resizeArray(colList, len); exprList = (Expression[]) ArrayUtil.resizeArray(exprList, len); CompiledStatement cs = new CompiledStatement(session, database, session.currentSchema, tableFilter, colList, exprList, condition, getSortedSubqueries(), getParameters()); return cs; } int parseOpenBracketsSelect() throws HsqlException { int count = parseOpenBrackets(); tokenizer.getThis(Token.T_SELECT); return count; } int parseOpenBrackets() throws HsqlException { int count = 0; while (tokenizer.isGetThis(Token.T_OPENBRACKET)) { count++; } return count; } int parseCloseBrackets(int limit) throws HsqlException { int count = 0; while (count < limit && tokenizer.isGetThis(Token.T_CLOSEBRACKET)) { count++; } return count; } HashMappedList parseColumnList() throws HsqlException { return processColumnList(tokenizer, false); } static HashMappedList processColumnList(Tokenizer tokenizer, boolean acceptAscDesc) throws HsqlException { HashMappedList list; String token; list = new HashMappedList(); tokenizer.getThis(Token.T_OPENBRACKET); while (true) { token = tokenizer.getSimpleName(); boolean result = list.add(token, null); if (!result) { throw Trace.error(Trace.COLUMN_ALREADY_EXISTS, token); } token = tokenizer.getSimpleToken(); if (acceptAscDesc && (token.equals(Token.T_DESC) || token.equals(Token.T_ASC))) { token = tokenizer.getSimpleToken(); // OJ: eat it up } if (token.equals(Token.T_COMMA)) { continue; } if (token.equals(Token.T_CLOSEBRACKET)) { break; } throw Trace.error(Trace.UNEXPECTED_TOKEN, token); } return list; } }