/* Copyrights and Licenses
*
* This product includes Hypersonic SQL.
* Originally developed by Thomas Mueller and the Hypersonic SQL Group.
*
* Copyright (c) 1995-2000 by 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.
* - All advertising materials mentioning features or use of this software must display the
* following acknowledgment: "This product includes Hypersonic SQL."
* - Products derived from this software may not be called "Hypersonic SQL" nor may
* "Hypersonic SQL" appear in their names without prior written permission of the
* Hypersonic SQL Group.
* - Redistributions of any form whatsoever must retain the following acknowledgment: "This
* product includes Hypersonic SQL."
* This software is provided "as is" and any expressed 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 its 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 any 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-2004, 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, including earlier
* license statements (above) and comply with all above license conditions.
*
* 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, including earlier
* license statements (above) and comply with all above license conditions.
*
* 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.io.IOException;
import java.io.LineNumberReader;
import java.io.StringReader;
import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.lib.ArrayUtil;
import org.hsqldb.lib.HashSet;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.StringUtil;
import org.hsqldb.scriptio.ScriptWriterBase;
import org.hsqldb.scriptio.ScriptWriterText;
/**
* Provides SQL Interpreter services relative to a Session and
* its Database.
*
* @version 1.7.2
* @since HSQLDB 1.7.2
*/
// fredt@users 20020430 - patch 549741 by velichko - ALTER TABLE RENAME
// fredt@users 20020405 - patch 1.7.0 - other ALTER TABLE statements
// tony_lai@users 20020820 - patch 595099 - use user-defined PK name
// tony_lai@users 20020820 - patch 595156 - violation of constraint name
// fredt@users 20020912 - patch 1.7.1 by fredt - log alter statements
// kloska@users 20021030 - patch 1.7.2 - ON UPDATE CASCADE | SET NULL | SET DEFAULT
// kloska@users 20021112 - patch 1.7.2 - ON DELETE SET NULL | SET DEFAULT
// boucherb@users 20020310 - disable ALTER TABLE DDL on VIEWs (avoid NPE)
// fredt@users 20030314 - patch 1.7.2 by gilead@users - drop table if exists syntax
// boucherb@users 20030425 - DDL methods are moved to DatabaseCommandInterpreter.java
// boucherb@users 20030425 - refactoring DDL methods into smaller units
// fredt@users 20030609 - support for ALTER COLUMN SET/DROP DEFAULT / RENAME TO
// wondersonic@users 20031205 - IF EXISTS support for DROP INDEX
// fredt@users 20031224 - support for CREATE SEQUENCE ...
class DatabaseCommandInterpreter {
Tokenizer tokenizer = new Tokenizer();
protected Database database;
protected Session session;
/**
* Constructs a new DatabaseCommandInterpreter for the given Session
*
* @param s session
*/
DatabaseCommandInterpreter(Session s) {
session = s;
database = s.getDatabase();
}
/**
* Executes the SQL String. This method is always called from a block
* synchronized on the database object.
*
* @param sql query
* @return the result of executing the given SQL String
*/
Result execute(String sql) {
Parser parser;
Result result;
String token;
int cmd;
Logger logger;
DatabaseManager.gc();
result = null;
cmd = Token.UNKNOWNTOKEN;
logger = database.logger;
try {
tokenizer.reset(sql);
parser = new Parser(session, database, tokenizer);
while (true) {
tokenizer.setPartMarker();
session.setScripting(false);
token = tokenizer.getString();
if (token.length() == 0) {
break;
}
cmd = Token.get(token);
if (cmd == Token.SEMICOLON) {
continue;
}
// TODO: build up list of Results in session context
// RE: ability to gen/retrieve multiple result sets
// EX: session.addResult(executePart(cmd, token, parser));
result = executePart(cmd, token, parser);
// PATCH -- needs executePart to return not null result
if (result.iMode == ResultConstants.ERROR) {
break;
}
if (session.getScripting()) {
logger.writeToLog(session, tokenizer.getLastPart());
}
}
} catch (Throwable t) {
/** @todo fredt - when out of memory error is thrown it does not get classified
* making scriptRunner vulnerable */
result = new Result(t, tokenizer.getLastPart());
}
return result == null ? Session.emptyUpdateCount
: result;
}
private Result executePart(int cmd, String token,
Parser parser) throws Throwable {
Result result = Session.emptyUpdateCount;
int brackets = 0;
switch (cmd) {
case Token.OPENBRACKET : {
brackets = Parser.parseOpenBrackets(tokenizer) + 1;
tokenizer.getThis(Token.T_SELECT);
}
case Token.SELECT : {
CompiledStatement cStatement =
parser.compileSelectStatement(brackets);
if (cStatement.parameters.length != 0) {
Trace.doAssert(
false,
Trace.getMessage(
Trace.ASSERT_DIRECT_EXEC_WITH_PARAM));
}
result = session.sqlExecuteCompiledNoPreChecks(cStatement);
break;
}
case Token.INSERT : {
CompiledStatement cStatement =
parser.compileInsertStatement();
if (cStatement.parameters.length != 0) {
Trace.doAssert(
false,
Trace.getMessage(
Trace.ASSERT_DIRECT_EXEC_WITH_PARAM));
}
result = session.sqlExecuteCompiledNoPreChecks(cStatement);
break;
}
case Token.UPDATE : {
CompiledStatement cStatement =
parser.compileUpdateStatement();
if (cStatement.parameters.length != 0) {
Trace.doAssert(
false,
Trace.getMessage(
Trace.ASSERT_DIRECT_EXEC_WITH_PARAM));
}
result = session.sqlExecuteCompiledNoPreChecks(cStatement);
break;
}
case Token.DELETE : {
CompiledStatement cStatement =
parser.compileDeleteStatement();
if (cStatement.parameters.length != 0) {
Trace.doAssert(
false,
Trace.getMessage(
Trace.ASSERT_DIRECT_EXEC_WITH_PARAM));
}
result = session.sqlExecuteCompiledNoPreChecks(cStatement);
break;
}
case Token.CALL : {
CompiledStatement cStatement = parser.compileCallStatement();
if (cStatement.parameters.length != 0) {
Trace.doAssert(
false,
Trace.getMessage(
Trace.ASSERT_DIRECT_EXEC_WITH_PARAM));
}
result = session.sqlExecuteCompiledNoPreChecks(cStatement);
break;
}
case Token.SET :
processSet();
break;
case Token.COMMIT :
processCommit();
break;
case Token.ROLLBACK :
processRollback();
break;
case Token.SAVEPOINT :
processSavepoint();
break;
case Token.CREATE :
processCreate();
database.setMetaDirty(true);
break;
case Token.ALTER :
processAlter();
database.setMetaDirty(true);
break;
case Token.DROP :
processDrop();
database.setMetaDirty(true);
break;
case Token.GRANT :
processGrantOrRevoke(true);
database.setMetaDirty(false);
break;
case Token.REVOKE :
processGrantOrRevoke(false);
database.setMetaDirty(true);
break;
case Token.CONNECT :
processConnect();
database.setMetaDirty(false);
session.setScripting(false);
break;
case Token.DISCONNECT :
processDisconnect();
session.setScripting(true);
break;
case Token.SCRIPT :
result = processScript();
break;
case Token.SHUTDOWN :
processShutdown();
break;
case Token.CHECKPOINT :
processCheckpoint();
break;
case Token.EXPLAIN :
result = processExplainPlan();
break;
case Token.RELEASE :
processReleaseSavepoint();
break;
default :
throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
}
return result;
}
/**
* Responsible for parsing and executing the SCRIPT SQL statement
*
* @return either an empty result or one in which each row is a DDL or DML
* @throws IOException
* @throws HsqlException
*/
private Result processScript() throws IOException, HsqlException {
String token = tokenizer.getString();
ScriptWriterText dsw;
if (tokenizer.wasValue()) {
token = (String) tokenizer.getAsValue();
dsw = new ScriptWriterText(database, token, true, true);
dsw.writeAll();
dsw.close();
return new Result(ResultConstants.UPDATECOUNT);
} else {
tokenizer.back();
session.checkAdmin();
return DatabaseScript.getScript(database, false);
}
}
/**
* Responsible for handling CREATE ...
*
* All CREATE command require an ADMIN user except:
*
*
* CREATE TEMP [MEMORY] TABLE
*
*
* @throws HsqlException
*/
private void processCreate() throws HsqlException {
String token;
boolean isTemp;
boolean unique;
int tableType;
session.checkReadWrite();
token = tokenizer.getString();
isTemp = false;
if (token.equals(Token.T_TEMP)) {
isTemp = true;
token = tokenizer.getString();
switch (Token.get(token)) {
case Token.TEXT :
session.checkAdmin();
// fall thru
case Token.TABLE :
case Token.MEMORY :
session.setScripting(false);
break;
default :
throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
}
} else {
session.checkAdmin();
session.checkDDLWrite();
session.setScripting(true);
}
// fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
unique = false;
tableType = 0;
switch (Token.get(token)) {
// table
case Token.TABLE :
tableType = isTemp ? Table.TEMP_TABLE
: Table.MEMORY_TABLE;
processCreateTable(tableType);
break;
case Token.MEMORY :
tokenizer.getThis(Token.T_TABLE);
tableType = isTemp ? Table.TEMP_TABLE
: Table.MEMORY_TABLE;
processCreateTable(tableType);
break;
case Token.CACHED :
tokenizer.getThis(Token.T_TABLE);
processCreateTable(Table.CACHED_TABLE);
break;
case Token.TEXT :
tokenizer.getThis(Token.T_TABLE);
tableType = isTemp ? Table.TEMP_TEXT_TABLE
: Table.TEXT_TABLE;
processCreateTable(tableType);
break;
// other objects
case Token.ALIAS :
processCreateAlias();
break;
case Token.SEQUENCE :
processCreateSequence();
break;
case Token.TRIGGER :
processCreateTrigger();
break;
case Token.USER :
processCreateUser();
break;
case Token.VIEW :
processCreateView();
break;
// index
case Token.UNIQUE :
unique = true;
tokenizer.getThis(Token.T_INDEX);
//fall thru
case Token.INDEX :
processCreateIndex(unique);
break;
default : {
throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
}
}
}
/**
* Process a bracketed column list as used in the declaration of SQL
* CONSTRAINTS and return an array containing the indexes of the columns
* within the table.
*
* @param t table that contains the columns
* @return column index map
* @throws HsqlException if a column is not found or is duplicate
*/
private int[] processColumnList(Table t) throws HsqlException {
HsqlArrayList list;
HashSet set;
String token;
int col[];
int size;
list = new HsqlArrayList();
set = new HashSet();
tokenizer.getThis(Token.T_OPENBRACKET);
while (true) {
token = tokenizer.getName();
list.add(token);
set.add(token);
if (list.size() != set.size()) {
throw Trace.error(Trace.COLUMN_ALREADY_EXISTS,
Trace.ERROR_IN_CONSTRAINT_COLUMN_LIST);
}
token = tokenizer.getString();
if (token.equals(Token.T_DESC) || token.equals(Token.T_ASC)) {
token = tokenizer.getString(); // 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);
}
size = list.size();
col = new int[size];
for (int i = 0; i < size; i++) {
col[i] = t.getColumnNr((String) list.get(i));
}
return col;
}
/**
* Responsible for tail end of CREATE INDEX DDL.
*
* Indexes defined in DDL scripts are handled by this method. If the
* name of an existing index begins with "SYS_", the name is changed to
* begin with "USER_". The name should be unique within the database.
* For compatibility with old database, non-unique names are modified
* and assigned a new name.
*
* In 1.7.2 no new index is created if an equivalent already exists.
*
* (fredt@users)
*
* @param t table
* @param indexName index
*
* @param indexNameQuoted is quoted
* @param unique is unique
* @throws HsqlException
*/
private void addIndexOn(Table t, String indexName,
boolean indexNameQuoted,
boolean unique) throws HsqlException {
HsqlName indexHsqlName;
int[] indexColumns;
if (database.indexNameList.containsName(indexName)) {
throw Trace.error(Trace.INDEX_ALREADY_EXISTS);
}
indexHsqlName = newIndexHsqlName(indexName, indexNameQuoted);
indexColumns = processColumnList(t);
session.commit();
session.setScripting(!t.isTemp());
TableWorks tableWorks = new TableWorks(session, t);
tableWorks.createIndex(indexColumns, indexHsqlName, unique, false,
false);
}
/**
* Responsible for handling the execution of CREATE TRIGGER SQL
* statements.
*
* typical sql is: CREATE TRIGGER tr1 AFTER INSERT ON tab1 CALL "pkg.cls"
*
* @throws HsqlException
*/
private void processCreateTrigger() throws HsqlException {
Table t;
boolean isForEach;
boolean isNowait;
int queueSize;
String triggerName;
boolean isQuoted;
String sWhen;
String sOper;
String tableName;
String token;
String className;
TriggerDef td;
Trigger o;
Class cl;
triggerName = tokenizer.getName();
checkTriggerExists(triggerName, false);
isQuoted = tokenizer.wasQuotedIdentifier();
isForEach = false;
isNowait = false;
queueSize = TriggerDef.getDefaultQueueSize();
sWhen = tokenizer.getString();
sOper = tokenizer.getString();
tokenizer.getThis(Token.T_ON);
tableName = tokenizer.getString();
t = database.getTable(session, tableName);
checkIsReallyTable(t);
// fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
session.setScripting(!t.isTemp());
// "FOR EACH ROW" or "CALL"
token = tokenizer.getString();
if (token.equals(Token.T_FOR)) {
token = tokenizer.getString();
if (token.equals(Token.T_EACH)) {
token = tokenizer.getString();
if (token.equals(Token.T_ROW)) {
isForEach = true;
// should be 'NOWAIT' or 'QUEUE' or 'CALL'
token = tokenizer.getString();
} else {
throw Trace.error(Trace.UNEXPECTED_END_OF_COMMAND, token);
}
} else {
throw Trace.error(Trace.UNEXPECTED_END_OF_COMMAND, token);
}
}
if (token.equals(Token.T_NOWAIT)) {
isNowait = true;
// should be 'CALL' or 'QUEUE'
token = tokenizer.getString();
}
if (token.equals(Token.T_QUEUE)) {
queueSize = tokenizer.getInt(); //queueSize = Integer.parseInt(tokenizer.getString());
// should be 'CALL'
token = tokenizer.getString();
}
if (!token.equals(Token.T_CALL)) {
throw Trace.error(Trace.UNEXPECTED_END_OF_COMMAND, token);
}
// PRE: double quotes have been stripped
className = tokenizer.getString();
try {
// dynamically load class
cl = classForName(className);
// dynamically instantiate it
o = (Trigger) cl.newInstance();
HsqlName name = database.nameManager.newHsqlName(triggerName,
isQuoted);
td = new TriggerDef(name, sWhen, sOper, isForEach, t, o,
"\"" + className + "\"", isNowait, queueSize);
if (td.isValid()) {
t.addTrigger(td);
// start the trigger thread
td.start();
} else {
throw Trace.error(Trace.UNEXPECTED_TOKEN,
Trace.CREATE_TRIGGER_COMMAND_1);
}
} catch (Exception e) {
throw Trace.error(Trace.UNKNOWN_FUNCTION,
Trace.CREATE_TRIGGER_COMMAND_2, e.getMessage());
}
// boucherb@users 20021128 - enforce unique trigger names
database.triggerNameList.addName(triggerName, t.getName(),
Trace.TRIGGER_ALREADY_EXISTS);
// --
}
/**
* Responsible for handling the creation of table columns during the
* process of executing CREATE TABLE DDL statements.
*
* @param t target table
* @return a Column object with indicated attributes
* @throws HsqlException
*/
private Column processCreateColumn(Table t) throws HsqlException {
boolean isIdentity = false;
long identityStart = database.firstIdentity;
long identityIncrement = 1;
boolean isPrimaryKey = false;
String columnName;
boolean isQuoted;
String typeName;
int type;
String sLen;
int length = 0;
String sScale;
int scale = 0;
boolean isNullable = true;
Expression defaultExpr = null;
String token = tokenizer.getString();
columnName = token;
Trace.check(!columnName.equals(Table.DEFAULT_PK),
Trace.COLUMN_ALREADY_EXISTS, columnName);
isQuoted = tokenizer.wasQuotedIdentifier();
typeName = tokenizer.getString();
type = Types.getTypeNr(typeName);
if (typeName.equals(Token.T_IDENTITY)) {
isIdentity = true;
isPrimaryKey = true;
}
// fredt - when SET IGNORECASE is in effect, all new VARCHAR columns are defined as VARCHAR_IGNORECASE
/**
* @todo fredt - drop support for SET IGNORECASE and replace the
* type name with a qualifier specifying the case sensitivity of VARCHAR
*/
if (type == Types.VARCHAR && database.isIgnoreCase()) {
type = Types.VARCHAR_IGNORECASE;
}
token = tokenizer.getString();
if (type == Types.DOUBLE && token.equals(Token.T_PRECISION)) {
token = tokenizer.getString();
}
// fredt@users 20020130 - patch 491987 by jimbag@users
sLen = "";
if (token.equals(Token.T_OPENBRACKET)) {
while (true) {
token = tokenizer.getString();
if (token.equals(Token.T_CLOSEBRACKET)) {
break;
}
sLen += token;
}
token = tokenizer.getString();
}
// see if we have a scale specified
int index;
if ((index = sLen.indexOf(Token.T_COMMA)) != -1) {
sScale = sLen.substring(index + 1, sLen.length());
sLen = sLen.substring(0, index);
Trace.check(Types.acceptsScaleCreateParam(type),
Trace.UNEXPECTED_TOKEN);
try {
scale = Integer.parseInt(sScale.trim());
} catch (NumberFormatException ne) {
throw Trace.error(Trace.UNEXPECTED_TOKEN, sLen);
}
}
// convert the length
if (!org.hsqldb.lib.StringUtil.isEmpty(sLen)) {
Trace.check(Types.acceptsPrecisionCreateParam(type),
Trace.UNEXPECTED_TOKEN);
try {
length = Integer.parseInt(sLen.trim());
} catch (NumberFormatException ne) {
throw Trace.error(Trace.UNEXPECTED_TOKEN, sLen);
}
}
if (token.equals(Token.T_DEFAULT)) {
defaultExpr = processCreateDefaultExpression(type, length);
token = tokenizer.getString();
} else if (token.equals(Token.T_GENERATED)) {
tokenizer.getThis(Token.T_BY);
tokenizer.getThis(Token.T_DEFAULT);
tokenizer.getThis(Token.T_AS);
tokenizer.getThis(Token.T_IDENTITY);
if (tokenizer.isGetThis(Token.T_OPENBRACKET)) {
tokenizer.getThis(Token.T_START);
tokenizer.getThis(Token.T_WITH);
try {
identityStart = tokenizer.getBigint();
} catch (NumberFormatException ne) {
throw Trace.error(Trace.UNEXPECTED_TOKEN, sLen);
}
if (tokenizer.isGetThis(Token.T_COMMA)) {
tokenizer.getThis(Token.T_INCREMENT);
tokenizer.getThis(Token.T_BY);
identityIncrement = tokenizer.getBigint();
}
tokenizer.getThis(Token.T_CLOSEBRACKET);
}
isIdentity = true;
isPrimaryKey = true;
token = tokenizer.getString();
}
// fredt@users - accept IDENTITY before or after NOT NULL
if (token.equals(Token.T_IDENTITY)) {
isIdentity = true;
isPrimaryKey = true;
token = tokenizer.getString();
}
if (token.equals(Token.T_NULL)) {
token = tokenizer.getString();
} else if (token.equals(Token.T_NOT)) {
tokenizer.getThis(Token.T_NULL);
isNullable = false;
token = tokenizer.getString();
}
if (token.equals(Token.T_IDENTITY)) {
if (isIdentity) {
throw Trace.error(Trace.SECOND_PRIMARY_KEY, Token.T_IDENTITY);
}
isIdentity = true;
isPrimaryKey = true;
token = tokenizer.getString();
}
if (token.equals(Token.T_PRIMARY)) {
tokenizer.getThis(Token.T_KEY);
isPrimaryKey = true;
} else {
tokenizer.back();
}
return new Column(
database.nameManager.newHsqlName(columnName, isQuoted),
isNullable, type, length, scale, isIdentity, identityStart,
identityIncrement, isPrimaryKey, defaultExpr);
}
/**
* @param type data type of column
* @param length maximum length of column
* @throws HsqlException
* @return new Expression
*/
private Expression processCreateDefaultExpression(int type,
int length) throws HsqlException {
if (type == Types.OTHER) {
throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE);
}
Parser parser = new Parser(session, database, tokenizer);
Expression expr = parser.readDefaultClause();
expr.resolveTypes();
if (expr.getType() == Expression.VALUE
|| (expr.getType() == Expression.FUNCTION
&& expr.function.isSimple)) {
Object defValTemp;
try {
defValTemp = expr.getValue(session, type);
} catch (HsqlException e) {
throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE);
}
if (defValTemp != null
&& (database.sqlEnforceSize
|| database.sqlEnforceStrictSize)) {
Object defValTest = Table.enforceSize(defValTemp, type,
length, false, false);
if (!defValTemp.equals(defValTest)) {
// default value is too long for fixed size column
throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE);
}
}
return expr;
}
throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE);
}
public static void checkBooleanDefault(String s,
int type) throws HsqlException {
if (type != Types.BOOLEAN || s == null) {
return;
}
s = s.toUpperCase();
if (s.equals(Token.T_TRUE) || s.equals(Token.T_FALSE)) {
return;
}
if (s.equals("0") || s.equals("1")) {
return;
}
throw Trace.error(Trace.WRONG_DEFAULT_CLAUSE, s);
}
/**
* Responsible for handling constraints section of CREATE TABLE ...
*
* @param t table
* @param constraint CONSTRAINT keyword used
* @param primarykeycolumn primary columns
* @throws HsqlException
* @return list of constraints
*/
private HsqlArrayList processCreateConstraints(Table t,
boolean constraint, int[] primarykeycolumn) throws HsqlException {
String token;
HsqlArrayList tcList;
Constraint tempConst;
HsqlName pkHsqlName;
// fredt@users 20020225 - comment
// HSQLDB relies on primary index to be the first one defined
// and needs original or system added primary key before any
// non-unique index is created
tcList = new HsqlArrayList();
tempConst = new Constraint(null, primarykeycolumn, null, null,
Constraint.MAIN, Constraint.NO_ACTION,
Constraint.NO_ACTION);
// tony_lai@users 20020820 - patch 595099
pkHsqlName = null;
tcList.add(tempConst);
if (!constraint) {
return tcList;
}
while (true) {
HsqlName cname = null;
if (tokenizer.isGetThis(Token.T_CONSTRAINT)) {
token = tokenizer.getName();
cname = database.nameManager.newHsqlName(token,
tokenizer.wasQuotedIdentifier());
}
token = tokenizer.getString();
switch (Token.get(token)) {
case Token.PRIMARY : {
tokenizer.getThis(Token.T_KEY);
// tony_lai@users 20020820 - patch 595099
pkHsqlName = cname;
int col[] = processColumnList(t);
Constraint mainConst;
mainConst = (Constraint) tcList.get(0);
if (mainConst.core.mainColArray != null) {
if (!ArrayUtil.areEqual(mainConst.core.mainColArray,
col, col.length, true)) {
throw Trace.error(Trace.SECOND_PRIMARY_KEY);
}
}
mainConst.core.mainColArray = col;
mainConst.constName = pkHsqlName;
break;
}
case Token.UNIQUE : {
int col[] = processColumnList(t);
if (cname == null) {
cname = database.nameManager.newAutoName("CT");
}
tempConst = new Constraint(cname, col, null, null,
Constraint.UNIQUE,
Constraint.NO_ACTION,
Constraint.NO_ACTION);
tcList.add(tempConst);
break;
}
case Token.FOREIGN : {
tokenizer.getThis(Token.T_KEY);
tempConst = processCreateFK(t, cname);
if (tempConst.core.refColArray == null) {
Constraint mainConst = (Constraint) tcList.get(0);
tempConst.core.refColArray =
mainConst.core.mainColArray;
if (tempConst.core.refColArray == null) {
throw Trace.error(Trace.INDEX_NOT_FOUND,
Trace.TABLE_HAS_NO_PRIMARY_KEY);
}
}
checkFKColumnDefaults(t, tempConst);
t.checkColumnsMatch(tempConst.core.mainColArray,
tempConst.core.refTable,
tempConst.core.refColArray);
tcList.add(tempConst);
break;
}
case Token.CHECK : {
if (cname == null) {
cname = database.nameManager.newAutoName("CT");
}
tempConst = new Constraint(cname, null, null, null,
Constraint.CHECK, 0, 0);
processCreateCheckConstraintCondition(tempConst);
tcList.add(tempConst);
break;
}
}
token = tokenizer.getString();
if (token.equals(Token.T_COMMA)) {
continue;
}
if (token.equals(Token.T_CLOSEBRACKET)) {
break;
}
throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
}
return tcList;
}
/**
* Responsible for handling check constraints section of CREATE TABLE ...
*
* @param c check constraint
* @throws HsqlException
*/
private void processCreateCheckConstraintCondition(Constraint c)
throws HsqlException {
tokenizer.getThis(Token.T_OPENBRACKET);
Parser parser = new Parser(session, database, tokenizer);
Expression condition = parser.parseExpression();
tokenizer.getThis(Token.T_CLOSEBRACKET);
c.core.check = condition;
}
/**
* Responsible for handling the execution CREATE TABLE SQL statements.
*
* @param type Description of the Parameter
* @throws HsqlException
*/
private void processCreateTable(int type) throws HsqlException {
Table t;
String token;
boolean isnamequoted;
int[] pkCols;
int colIndex;
boolean constraint;
HsqlArrayList tempConstraints;
token = tokenizer.getName();
checkTableExists(token, false);
isnamequoted = tokenizer.wasQuotedIdentifier();
t = newTable(type, token, isnamequoted);
tokenizer.getThis(Token.T_OPENBRACKET);
pkCols = null;
colIndex = 0;
constraint = false;
while (true) {
token = tokenizer.getString();
isnamequoted = tokenizer.wasQuotedIdentifier();
// fredt@users 20020225 - comment
// we can check here for reserved words used with
// quotes as column names
switch (Token.get(token)) {
case Token.CONSTRAINT :
case Token.PRIMARY :
case Token.FOREIGN :
case Token.UNIQUE :
case Token.CHECK :
constraint = true;
}
tokenizer.back();
if (constraint) {
break;
}
Column newcolumn = processCreateColumn(t);
t.addColumn(newcolumn);
if (newcolumn.isPrimaryKey()) {
Trace.check(pkCols == null, Trace.SECOND_PRIMARY_KEY,
newcolumn.columnName.name);
pkCols = new int[]{ colIndex };
}
token = tokenizer.getString();
if (token.equals(Token.T_COMMA)) {
colIndex++;
continue;
}
if (token.equals(Token.T_CLOSEBRACKET)) {
break;
}
throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
}
tempConstraints = processCreateConstraints(t, constraint, pkCols);
try {
session.commit();
Constraint primaryConst = (Constraint) tempConstraints.get(0);
if (primaryConst.constName != null) {
database.indexNameList.addName(primaryConst.constName.name,
t.getName(),
Trace.INDEX_ALREADY_EXISTS);
database.constraintNameList.addName(
primaryConst.constName.name, t.getName(),
Trace.CONSTRAINT_ALREADY_EXISTS);
}
t.createPrimaryKey(primaryConst.constName,
primaryConst.core.mainColArray, true);
for (int i = 1; i < tempConstraints.size(); i++) {
Constraint tempConst = (Constraint) tempConstraints.get(i);
if (tempConst.constType == Constraint.UNIQUE) {
TableWorks tableWorks = new TableWorks(session, t);
tableWorks.createUniqueConstraint(
tempConst.core.mainColArray, tempConst.constName);
t = tableWorks.getTable();
}
if (tempConst.constType == Constraint.FOREIGN_KEY) {
TableWorks tableWorks = new TableWorks(session, t);
tableWorks.createForeignKey(tempConst.core.mainColArray,
tempConst.core.refColArray,
tempConst.constName,
tempConst.core.refTable,
tempConst.core.deleteAction,
tempConst.core.updateAction);
t = tableWorks.getTable();
}
if (tempConst.constType == Constraint.CHECK) {
TableWorks tableWorks = new TableWorks(session, t);
tableWorks.createCheckConstraint(tempConst,
tempConst.constName);
t = tableWorks.getTable();
}
}
database.linkTable(t);
} catch (HsqlException e) {
// fredt@users 20020225 - comment
// if a HsqlException is thrown while creating table, any foreign key that has
// been created leaves it modification to the expTable in place
// need to undo those modifications. This should not happen in practice.
database.removeExportedKeys(t);
database.indexNameList.removeOwner(t.tableName);
database.constraintNameList.removeOwner(t.tableName);
throw e;
}
}
/**
* @param t table
* @param cname foreign key name
* @throws HsqlException
* @return constraint
*/
private Constraint processCreateFK(Table t,
HsqlName cname) throws HsqlException {
int[] localcol;
int[] expcol;
String expTableName;
Table expTable;
String token;
localcol = processColumnList(t);
tokenizer.getThis(Token.T_REFERENCES);
expTableName = tokenizer.getString();
// fredt@users 20020221 - patch 520213 by boucherb@users - self reference FK
// allows foreign keys that reference a column in the same table
if (t.equals(expTableName)) {
expTable = t;
} else {
expTable = database.getTable(session, expTableName);
}
expcol = null;
token = tokenizer.getString();
tokenizer.back();
// fredt@users 20020503 - patch 1.7.0 by fredt - FOREIGN KEY on table
if (token.equals(Token.T_OPENBRACKET)) {
expcol = processColumnList(expTable);
} else {
// the exp table must have a user defined primary key
Index expIndex = expTable.getPrimaryIndex();
if (expIndex != null) {
expcol = expIndex.getColumns();
if (expcol[0] == expTable.getColumnCount()) {
throw Trace.error(Trace.INDEX_NOT_FOUND,
Trace.TABLE_HAS_NO_PRIMARY_KEY,
new Object[]{ expTableName });
}
}
// with CREATE TABLE, (expIndex == null) when self referencing FK
// is declared in CREATE TABLE
// null will be returned for expCol and will be checked
// in caller method
// with ALTER TABLE, (expIndex == null) when table has no PK
}
token = tokenizer.getString();
// -- In a while loop we parse a maximium of two
// -- "ON" statements following the foreign key
// -- definition this can be
// -- ON [UPDATE|DELETE] [NO ACTION|RESTRICT|CASCADE|SET [NULL|DEFAULT]]
int deleteAction = Constraint.NO_ACTION;
int updateAction = Constraint.NO_ACTION;
while (token.equals(Token.T_ON)) {
token = tokenizer.getString();
if (deleteAction == Constraint.NO_ACTION
&& token.equals(Token.T_DELETE)) {
token = tokenizer.getString();
if (token.equals(Token.T_SET)) {
token = tokenizer.getString();
if (token.equals(Token.T_DEFAULT)) {
deleteAction = Constraint.SET_DEFAULT;
} else if (token.equals(Token.T_NULL)) {
deleteAction = Constraint.SET_NULL;
} else {
throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
}
} else if (token.equals(Token.T_CASCADE)) {
deleteAction = Constraint.CASCADE;
} else if (token.equals(Token.T_RESTRICT)) {
// LEGACY compatibility/usability
// - same as NO ACTION or nothing at all
} else {
tokenizer.getCurrentThis(Token.T_NO);
tokenizer.getThis(Token.T_ACTION);
}
} else if (updateAction == Constraint.NO_ACTION
&& token.equals(Token.T_UPDATE)) {
token = tokenizer.getString();
if (token.equals(Token.T_SET)) {
token = tokenizer.getString();
if (token.equals(Token.T_DEFAULT)) {
updateAction = Constraint.SET_DEFAULT;
} else if (token.equals(Token.T_NULL)) {
updateAction = Constraint.SET_NULL;
} else {
throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
}
} else if (token.equals(Token.T_CASCADE)) {
updateAction = Constraint.CASCADE;
} else if (token.equals(Token.T_RESTRICT)) {
// LEGACY compatibility/usability
// - same as NO ACTION or nothing at all
} else {
tokenizer.getCurrentThis(Token.T_NO);
tokenizer.getThis(Token.T_ACTION);
}
} else {
throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
}
token = tokenizer.getString();
}
tokenizer.back();
if (cname == null) {
cname = database.nameManager.newAutoName("FK");
}
return new Constraint(cname, localcol, expTable, expcol,
Constraint.FOREIGN_KEY, deleteAction,
updateAction);
}
/**
* Responsible for handling the execution CREATE VIEW SQL statements.
*
* @throws HsqlException
*/
private void processCreateView() throws HsqlException {
String token = tokenizer.getName();
int logposition = tokenizer.getPartMarker();
checkViewExists(token, false);
HsqlName viewHsqlName = database.nameManager.newHsqlName(token,
tokenizer.wasQuotedIdentifier());
HsqlName[] colList = null;
if (tokenizer.isGetThis(Token.T_OPENBRACKET)) {
HsqlArrayList list = Parser.getColumnNames(database, tokenizer,
true);
colList = new HsqlName[list.size()];
colList = (HsqlName[]) list.toArray(colList);
}
tokenizer.getThis(Token.T_AS);
tokenizer.setPartMarker();
Parser parser = new Parser(session, database, tokenizer);
int brackets = 0;
if (tokenizer.isGetThis(Token.T_OPENBRACKET)) {
brackets += Parser.parseOpenBrackets(tokenizer) + 1;
}
tokenizer.getThis(Token.T_SELECT);
Select select;
// do not accept LIMIT and ORDER BY - accept unions
select = parser.parseSelect(brackets, false, true);
if (select.sIntoTable != null) {
throw (Trace.error(Trace.TABLE_NOT_FOUND));
}
select.prepareResult();
View view = new View(database, viewHsqlName, tokenizer.getLastPart(),
colList);
session.commit();
database.linkTable(view);
tokenizer.setPartMarker(logposition);
}
/**
* Responsible for handling tail of ALTER TABLE ... RENAME ...
* @param t table
* @throws HsqlException
*/
private void processAlterTableRename(Table t) throws HsqlException {
String tableName;
String newName;
boolean isquoted;
tableName = t.getName().name;
// ensures that if temp table, it also belongs to this session
if (!t.equals(session, tableName)) {
throw Trace.error(Trace.TABLE_NOT_FOUND);
}
tokenizer.getThis(Token.T_TO);
newName = tokenizer.getName();
isquoted = tokenizer.wasQuotedIdentifier();
checkTableExists(newName, false);
session.commit();
session.setScripting(!t.isTemp());
t.renameTable(newName, isquoted);
}
/**
* Handles ALTER TABLE statements.
*
* ALTER TABLE RENAME TO
* ALTER INDEX RENAME TO
*
* ALTER TABLE ADD CONSTRAINT FOREIGN KEY (