src.com.ibm.as400.access.JDSQLStatement Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jt400-jdk8 Show documentation
Show all versions of jt400-jdk8 Show documentation
The Open Source version of the IBM Toolbox for Java
The newest version!
///////////////////////////////////////////////////////////////////////////////
//
// JTOpen (IBM Toolbox for Java - OSS version)
//
// Filename: JDSQLStatement.java
//
// The source code contained herein is licensed under the IBM Public License
// Version 1.0, which has been approved by the Open Source Initiative.
// Copyright (C) 1997-2002 International Business Machines Corporation and
// others. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////////
package com.ibm.as400.access;
import java.sql.Connection; // @G4A
import java.sql.SQLException;
import java.util.StringTokenizer;
import java.util.Vector;
/**
Internal class that represents a parsed SQL statement.
This should only be used internally by the JDBC driver.
**/
//
// Implementation note:
//
// Originally, the statement was parsed as information
// was needed. For example, it did not parse to see
// if "FOR UPDATE" appeared until isForUpdate() was
// called. This strategy caused a lot of extraneous
// parsing, and most of the information is needed for
// most statements, so now all parsing is done at object
// construction time.
//
public class JDSQLStatement
{
// Native statement types.
//
static final int TYPE_UNDETERMINED = 0;
static final int TYPE_OTHER = 1;
static final int TYPE_SELECT = 2;
static final int TYPE_CALL = 3;
static final int TYPE_COMMIT = 4;
static final int TYPE_ROLLBACK = 5;
static final int TYPE_CONNECT = 6;
static final int TYPE_BLOCK_INSERT = 7;
// String constants. These will hopefully help performance
// slightly - assuming a similar optimization does not
// already take place.
private static final String AS_ = "AS";
private static final String CALL_ = "CALL";
private static final String CALL0_ = "?"; // @E1A
private static final String CALL1_ = "?=";
private static final String CALL2_ = "?=CALL";
static final String COMMA_ = ",";
private static final String CONNECT_ = "CONNECT";
private static final String CONNECTION_ = "CONNECTION"; // @F1A
static final String CROSS_ = "CROSS";
private static final String CURRENT_ = "CURRENT";
private static final String DECLARE_ = "DECLARE";
private static final String DELETE_ = "DELETE";
private static final String DISCONNECT_ = "DISCONNECT";
static final String EXCEPTION_ = "EXCEPTION";
private static final String FETCH_ = "FETCH";
private static final String FOR_ = "FOR";
private static final String FROM_ = "FROM";
static final String INNER_ = "INNER";
private static final String INSERT_ = "INSERT";
static final String JOIN_ = "JOIN";
static final String LEFT_ = "LEFT";
private static final String LPAREN_ = "(";
private static final String OF_ = "OF";
private static final String ONLY_ = "ONLY";
private static final String READ_ = "READ";
private static final String RELEASE_ = "RELEASE";
private static final String ROWS_ = "ROWS";
private static final String SELECT_ = "SELECT";
private static final String SET_ = "SET";
private static final String UPDATE_ = "UPDATE";
private static final String VALUES_ = "VALUES";
private static final String WITH_ = "WITH"; // @B3A
private static final String MERGE_ = "MERGE"; //@blksql
static final String METADATA_CALL = "CALL SYSIBM"; /*@K5A*/
private boolean canBeBatched_ = false; // @H2A
private String correlationName_ = null;
private String csProcedure_ = null; // @G4A
private String csSchema_ = null; // @G4A
private boolean hasReturnValueParameter_ = false; // @E1A
private boolean isCall_ = false;
private boolean isDeclare_ = false;
private boolean isCurrentOf_ = false;
private boolean isDRDAConnect_ = false; // @B1A
private boolean isDRDADisconnect_ = false; // @B1A
private boolean isForFetchOrReadOnly_ = false;
private boolean isForUpdate_ = false;
private boolean isImmediatelyExecutable_ = false;
boolean isInsert_ = false; // @H2C Made not private.
private boolean isSelect_ = false;
private boolean isSet_ = false; // @F4A
private boolean isSetSpecialRegister_ = false;
private boolean isSubSelect_ = false;
private boolean isPackaged_ = false;
private boolean isUpdateOrDelete_ = false;
private int nativeType_ = TYPE_OTHER;
private int numberOfParameters_;
private String selectTable_ = null;
// @C3D private StringTokenizer tokenizer_ = null;
private JDSQLTokenizer tokenizer_ = null; // @C3A
private String value_;
// private String valueForServer_ = null; // @E1A
private boolean selectTableNotSet_ = true; //@K1A boolean to determine if selectTable_ has been set, if so, then selectTableNotSet_ is false
private boolean selectFromInsert_ = false; // @GKA
private boolean isMetaDataCall_ = false; //@K5A
// Contains a list of AS400JDBCStatementListener objects to be invoked when events occur
// related to a JDSQLStatement.
private static final Vector statementListeners_ = new Vector();
// Load any statement listeners that are specified in a runtime property.
static
{
String classNames = SystemProperties.getProperty(SystemProperties.JDBC_STATEMENT_LISTENERS);
if (classNames != null)
{
StringTokenizer st = new StringTokenizer(classNames, ",");
while (st.hasMoreTokens())
{
try
{
String clazz = st.nextToken();
addStatementListener((AS400JDBCStatementListener)Class.forName(clazz).newInstance());
Trace.log(Trace.INFORMATION, "Successfully loaded JDBC statement listener "+clazz);
}
catch (Throwable t)
{
Trace.log(Trace.WARNING, "Error instantiating JDBC statement listener: ", t);
}
}
}
}
/**
* Adds an AS400JDBCStatementListener.
* @param listener
**/
public static void addStatementListener(AS400JDBCStatementListener listener)
{
if (listener != null) statementListeners_.add(listener);
}
/**
* Removes an AS400JDBCStatementListener.
* @param listener
**/
public static void removeStatementListener(AS400JDBCStatementListener listener)
{
if (listener != null) statementListeners_.remove(listener);
}
/**
Constructs a JDSQLStatement object. Use this constructor
if you know you do not need to do any conversion to native
SQL.
@param sql A SQL statement.
@exception SQLException If there is a syntax error or
a reference to an unsupported
scalar function.
**/
JDSQLStatement(String sql) throws SQLException
{
// decimalSeparator is "" because we only need it if convert is true
// this constructor is only used for SET TRANSACTION ISOLATION LEVEL called
// internally, so the last two parms can be null -- this code would need to
// be changed if this constructor were used for a CALL statement with named
// parameters
this(sql, "", false, JDProperties.PACKAGE_CRITERIA_DEFAULT, null); // @A1C //@G1C
}
/**
Constructs a JDSQLStatement object.
@param sql A SQL statement.
@param decimalSeparator The decimal separator.
@param convert Convert to native SQL?
@param packageCriteria The package criteria.
@param connection A connection object to get properties off.
@exception SQLException If there is a syntax error or
a reference to an unsupported
scalar function.
**/
JDSQLStatement(String sql, String decimalSeparator, boolean convert, String packageCriteria,
AS400JDBCConnection connection) // @A1C //@G1C
throws SQLException
{
if(sql == null)
{
JDError.throwSQLException(JDError.EXC_SYNTAX_ERROR);
}
// Ensure that the string always contains at least one
// character, since some methods depend on that fact.
if(sql.trim().length() == 0)
{
JDError.throwSQLException(JDError.EXC_SYNTAX_BLANK);
}
//@F6D // Count the number of parameters. Do not count parameter
//@F6D // markers that appear within quotes or after a comment
//@F6D // delimiter (two dashes).
//@F6D numberOfParameters_ = 0;
//@F6D int commentIndex = -1;
//@F6D int length = sql.length();
//@F6D for (int i = 0; i < length; ++i)
//@F6D {
//@F6D char ch = sql.charAt(i);
//@F6D if (ch == '\'')
//@F6D {
//@F6D while ((i < length - 1) && (sql.charAt(++i) != '\''));
//@F6D }
//@F6D else if (ch == '\"')
//@F6D {
//@F6D while ((i < length - 1) && (sql.charAt (++i) != '\"'));
//@F6D }
//@F6D else if (ch == '-')
//@F6D {
//@F6D if (i < length - 1)
//@F6D {
//@F6D if (sql.charAt(++i) == '-')
//@F6D {
//@F6D commentIndex = i;
//@F6D i = length; // break out of for loop @F2C
//@F6D }
//@F6D }
//@F6D }
//@F6D else if (ch == '?')
//@F6D {
//@F6D ++numberOfParameters_;
//@F6D }
//@F6D }
//@F6A Start new code
// Strip off comments. Don't strip comment characters in literals.
int length = sql.length();
//char[] sqlArray = sql.toCharArray();
//char[] outArray = new char[length];
// @C3D int out = -1; // We are always pre-incrementing... so start before the first char here.
// Perf Note: numberOfParameters_ will default to 0. Don't set it here.
//@G8D boolean inComment = false;
//@G7 Skip removing comments if it is a CREATE statement smaller than 32KB
//@G7 (Greater than 32KB would overflow the buffer, so we have to remove
//@G7 the comments in that case.)
//@C3D if (length > 32767 || //@G7A
//@C3D !sql.trim().toUpperCase().startsWith("CREATE")) //@G7A
//@C3D{
//@G7 If this is not a normal CREATE or the length is too big,
//@G7 we must strip out the comments!
// @C3D for (int i = 0; i < length; ++i) {
// @C3D switch (sqlArray[i]) {
// @C3D case '\'':
// @C3D outArray[++out] = sqlArray[i];
// Consume everything while looking for the ending quote character.
// @C3D while (i < length - 1) {
// @C3D outArray[++out] = sqlArray[++i];
// @C3D if (sqlArray[i] == '\'') {
// @C3D break;
// @C3D }
// @C3D }
// @C3D break;
// @C3D case '\"':
// @C3D outArray[++out] = sqlArray[i];
// Consume everything while looking for the ending quote character.
// @C3D while (i < length - 1) {
// @C3D outArray[++out] = sqlArray[++i];
// @C3D if (sqlArray[i] == '\"') {
// @C3D break;
// @C3D }
// @C3D }
// @C3D break;
// @C3D case '-':
// @C3D if (i < length - 1) {
// @C3D if (sqlArray[++i] == '-') {
// It's a single line commment (--). We are going to eat the comment until
// a new line character or the end of the string, but first output a space
// to the output buffer.
// @C3D outArray[++out] = ' ';
// @C3D while ((i < length - 1) && (sqlArray[++i] != '\n'))
// @C3D ; // do nothing but spin.
// If we didn't break the loop because we were at the end of the string...
// we broke because of a newline character. Put it into the output.
// @C3D if (i < length - 1)
// @C3D outArray[++out] = '\n';
// @C3D }
// @C3D else {
// This was not a comment. Output the characters we read.
// @C3D outArray[++out] = sqlArray[i-1];
// @C3D outArray[++out] = sqlArray[i];
// @C3D }
// @C3D }
// @C3D else {
// Last character - must write the '-'
// @C3D outArray[++out] = sqlArray[i];
// @C3D }
// @C3D break;
// @C3D case '/':
// If we are not on the last character....
// @C3D if (i < length - 1) {
// Check to see if we are starting a comment.
// @C3D if (sqlArray[++i] == '*') {
// It is a multi-line commment - write over the '/*' characters
// and set the inComment flag.
// @C3D outArray[++out] = ' ';
//@G8D inComment = true;
// @C3D int numComments = 1; //@G8A - keep track of the nesting level
//@G8C
// Need to handle nested comments.
// If we see a */, we've closed a comment block.
// If we see another /*, we've started a new block.
// @C3D while (i < length - 1 && numComments > 0) //@G8C
// @C3D {
// @C3D char cur = sqlArray[++i]; //@G8A
// @C3D if (i < length-1)
// @C3D {
// @C3D char next = sqlArray[i+1];
// @C3D if (cur == '*' && next == '/')
// @C3D {
// @C3D --numComments;
// @C3D ++i;
// @C3D }
// @C3D else if (cur == '/' && next == '*')
// @C3D {
// @C3D ++numComments;
// @C3D ++i;
// @C3D }
// @C3D }
// @C3D }
// @C3D }
// @C3D else {
// This was not a comment. Output the characters we read.
// @C3D outArray[++out] = sqlArray[i-1];
// @C3D outArray[++out] = sqlArray[i];
// @C3D }
// @C3D }
// @C3D else {
// Last character - must write the '/'
// @C3D outArray[++out] = sqlArray[i];
// @C3D }
// @C3D break;
// @C3D case '?':
// Write the character.
// @C3D outArray[++out] = sqlArray[i];
// @C3D ++numberOfParameters_;
// @C3D break;
// @C3D default:
// Write the character.
// @C3D outArray[++out] = sqlArray[i];
// @C3D break;
// @C3D }
// @C3D }
for (int i=0; i= JDUtilities.vrm540) ? 2097151 : 32767; //@PDC 2M for commentStrip in v5r4
if(length > commentStripLength)//@PDC 2M for commentStrip
{ // @C3A
String old = sql;
JDSQLTokenizer commentStripper = new JDSQLTokenizer(sql, JDSQLTokenizer.DEFAULT_DELIMITERS, true, false); // @C3A
sql = commentStripper.toString(); // @C3M
// Notify our listeners that we stripped comments.
if (old.length() != sql.length())
{
for (int i=0; i= 0)
//@F6D {
//@F6D value_ = JDEscapeClause.parse(sql.substring(0, commentIndex), decimalSeparator) + sql.substring(commentIndex);
//@F6D }
//@F6D else
//@F6D {
value_ = JDEscapeClause.parse(sql, decimalSeparator, connection.getVRM()); // @C1M
//@F6D }
}
else
{
value_ = sql;
}
value_ = AS400BidiTransform.convertSQLToHostCCSID(value_, connection); //Bidi-HCG end
tokenizer_ = new JDSQLTokenizer(value_, JDSQLTokenizer.DEFAULT_DELIMITERS, false, false); // @C3A moved this line up from below
numberOfParameters_ = tokenizer_.getNumberOfParameters(); // @C3A the tokenizer counts the number of parameter markers now
// Determine the first word.
// @F2 - We need to skip any leading parentheses and whitespace and combinations thereof.
// e.g. SELECT
// (SELECT
// ( SELECT
// ((SELECT
// ( ( SELECT
String firstWord = ""; //@F2C
// @C3D tokenizer_ = new StringTokenizer(value_);
//@KBD while(firstWord == "" && tokenizer_.hasMoreTokens()) //@F2A
while(firstWord.length() == 0 && tokenizer_.hasMoreTokens()) //@KBA
{
String word = tokenizer_.nextToken();
// Our StringTokenizer ensures that word.length() > 0
int i=0;
int len = word.length();
while(i < len && word.charAt(i) == '(')
{
++i;
}
if(i < len)
{
firstWord = word.substring(i).toUpperCase();
}
}
//@F2D if (tokenizer_.countTokens() > 0)
//@F2D { // @E2C
//@F2D firstWord = tokenizer_.nextToken().toUpperCase();
//@F2D boolean flag = true; // @E2A
//@F2D while (flag)
//@F2D { // @E2A
//@F2D if (firstWord.length() == 0) // @E2A
//@F2D {
//@F2D flag = false; // @E2A
//@F2D }
//@F2D else if (firstWord.charAt(0) == '(') // @E2A
//@F2D {
//@F2D firstWord = firstWord.substring(1); // @E2A
//@F2D }
//@F2D else // @E2A
//@F2D {
//@F2D flag = false; // @E2A
//@F2D }
//@F2D } // @E2A
//@F2D } // @E2A
//@F2D else
//@F2D {
//@F2D firstWord = "";
//@F2D }
// Handle the statement based on the first word
if((firstWord.startsWith(SELECT_)) || (firstWord.equals(WITH_)) || (firstWord.startsWith(VALUES_))) // @F5C //@VALc //@val2
{
// @B3C
isSelect_ = true;
nativeType_ = TYPE_SELECT;
}
else if((firstWord.equals(CALL_)))
{ // @E1A
isCall_ = true;
nativeType_ = TYPE_CALL;
// Check for metadata call @K5A
if (value_.indexOf(METADATA_CALL) == 0) {
isMetaDataCall_ = true;
}
if (value_.indexOf("WLM_SET_CLIENT_INFO") > 0) {
isSetSpecialRegister_ = true;
}
}
else if((firstWord.equals(CALL0_)) || (firstWord.equals(CALL1_)) || (firstWord.equals(CALL2_))) //@E1A
{
// @E1A
isCall_ = true; // @E1A
nativeType_ = TYPE_CALL; // @E1A
hasReturnValueParameter_ = true; // @E1A
--numberOfParameters_; // We will "fake" the first one. // @E1A
} // @E1A
// @E10 moved Release to its own block
else if(firstWord.equals(CONNECT_) || firstWord.equals(CONNECTION_) || firstWord.equals(DISCONNECT_)) //@F1C @E10c
{
nativeType_ = TYPE_CONNECT;
if(firstWord.equals(CONNECT_)) // @B1A
{
isDRDAConnect_ = true; // @B1A
}
else if(firstWord.equals(DISCONNECT_)) // @B1A
{
isDRDADisconnect_ = true; // @B1A
}
}
// @E10 now release can be both a DRDC connection release or a savepoint release
else if(firstWord.equals(RELEASE_)) //@E10a
{
//@E10a
String upperCaseSql = value_.toUpperCase(); //@E10a
int k = upperCaseSql.indexOf("SAVEPOINT"); //@E10a
if(k >= 0) //@E10a
{
} // no need to do anything //@E10a
else //@E10a
nativeType_ = TYPE_CONNECT; //@E10a
} //@E10a
else if(firstWord.equals(INSERT_))
{
isInsert_ = true;
// Look for the string ROWS VALUES in the string.
String upperCaseSql = value_.toUpperCase();
int k = upperCaseSql.indexOf(ROWS_);
int len = upperCaseSql.length(); //@F2A @H2M
if(k != -1)
{
k += 4;
while(k < len && Character.isWhitespace(upperCaseSql.charAt(k))) //@F2C
{
++k;
}
if(upperCaseSql.regionMatches(k, VALUES_, 0, 6))
{
nativeType_ = TYPE_BLOCK_INSERT;
}
}
//@H2A Look for VALUES clause to determine whether this statement can be block inserted.
//@H2A The database will throw out block insert statements that have values other than
//@H2A parameter markers, like "INSERT INTO TABLE VALUES (NULL, ?)".
int m = upperCaseSql.indexOf(VALUES_); //@H2A
if(m != -1) //@H2A
{
//@H2A
m += 6; //@H2A
while(m < len && Character.isWhitespace(upperCaseSql.charAt(m))) //@H2A
{
//@H2A
++m; //@H2A
} //@H2A
//@H2A
int lastParen = upperCaseSql.lastIndexOf(")"); //@H2A
//@J1 - According to the DB2 SQL Reference, paranthesis are optional around a single value
//@J1D if(lastParen < m)
//@J1D {
//@J1D JDError.throwSQLException(this, JDError.EXC_SYNTAX_ERROR);
//@J1D }
if(lastParen >= 1 && lastParen > m) //@J1A make sure last parenthesis is after the VALUES keyword
{ //@J1A
String valuesString = upperCaseSql.substring(m+1, lastParen); //@H2A
StringTokenizer tokenizer = new StringTokenizer (valuesString, ", ?\n\r\t"); //@H2A
if(!tokenizer.hasMoreTokens()) //@H2A
{
//@H2A
canBeBatched_ = true; //@H2A
} //@H2A
} //@J1A
else //@J1A Check to see if only one value or one parameter marker exists, if more than one value, then it is a syntax error
{ //@J1A
String valuesString = upperCaseSql.substring(m); //@J1A - get everything after the VALUES clause
if(valuesString.trim().equals("?")) //@J1A - there is only one parameter marker, therefore, we can batch
canBeBatched_ = true; //@J1A
else //@J1A - make sure there is only one literal, otherwise a syntax error occured.
{ //@J1A
// @A1D
// If a syntax error can occur, leave it to the engine to throw the exception.
// This code to throw an exception was removed because after V6R1 the syntax
// "insert into x values 1,2,3" is valid
//StringTokenizer tokenizer = new StringTokenizer(valuesString, ","); //@J1A
//if(tokenizer.countTokens() > 1) //@J1A
// JDError.throwSQLException(this, JDError.EXC_SYNTAX_ERROR); //@J1A
} //@J1A
} //@J1A
}
}
else if((firstWord.equals(UPDATE_)) || (firstWord.equals(DELETE_)))
{
if(connection.getVRM() >= JDUtilities.vrm710) { //@blksql
if (((AS400JDBCConnection)connection).doUpdateDeleteBlocking()) {
canBeBatched_ = true; //@blksql
}
}
isUpdateOrDelete_ = true;
}
else if(firstWord.equals(MERGE_)) //@blksql
{
if((connection).getVRM() >= JDUtilities.vrm710) { //@blksql
canBeBatched_ = true; //@blksql
}
}
else if(firstWord.equals(DECLARE_))
{
isDeclare_ = true;
}
else if(firstWord.equals(SET_)) // @F4A - This entire block.
{
isSet_ = true;
nativeType_ = TYPE_UNDETERMINED;
// Note: See loop below for SET CONNECTION.
}
//@G4A New code starts
if(isCall_)
{
// Strip off extra '?'s or '='s
while(tokenizer_.hasMoreTokens() && !firstWord.endsWith(CALL_))
{
firstWord = tokenizer_.nextToken ().toUpperCase ();
}
String token = tokenizer_.nextToken();
int index = token.indexOf('(');
// Strip off the beginning of the parameters.
if(index != -1)
token = token.substring(0, index);
String namingSeparator;
if((connection).getProperties().
getString(JDProperties.NAMING).equalsIgnoreCase("sql"))
{
namingSeparator = ".";
}
else {
namingSeparator = "/";
}
//@DELIMa Added the following block to handle case sensitive procedure names and collection names
String qualifiedProcedure = null; // + procedure
StringBuffer buf = null; // for appending additional tokens
int schemaDelimEndIndex = -1; //@PDA flag used to preserve index inside qualifiedProcedure if schema is delim
// If no quotes, and there's a separator in the middle, no need to peekToken().
int separatorPos = token.indexOf(namingSeparator);
if(token.indexOf('\"') == -1 && separatorPos > 0 && separatorPos < token.length()-1)
{
qualifiedProcedure = token;
}
else if(token.endsWith(namingSeparator)) // schema not quoted, procedure is quoted
{
if(tokenizer_.hasMoreTokens()) {
buf = new StringBuffer(token);
buf.append(tokenizer_.nextToken());
}
}
else if(tokenizer_.hasMoreTokens() && tokenizer_.peekToken().equals(namingSeparator)) // both schema and procedure are quoted
{
schemaDelimEndIndex = token.length();
buf = new StringBuffer(token);
buf.append(tokenizer_.nextToken());
if(tokenizer_.hasMoreTokens()) {
buf.append(tokenizer_.nextToken());
}
}
else if(tokenizer_.hasMoreTokens() && tokenizer_.peekToken().startsWith(namingSeparator)) // schema is quoted (and maybe procedure too)
{
schemaDelimEndIndex = token.length();
buf = new StringBuffer(token);
buf.append(tokenizer_.nextToken());
}
else
{
qualifiedProcedure = token; // neither schema nor procedure is quoted
}
if(buf != null)
{
qualifiedProcedure = buf.toString();
int parenPos = qualifiedProcedure.indexOf('(');
// Strip off the beginning of the parameters.
if(parenPos != -1)
qualifiedProcedure = qualifiedProcedure.substring(0, parenPos);
}
buf = null; // we're done with the buffer
// @PDC preserve index if schema is quoted, since it may contain a namingSeparator (ie. "De.lim"."My.Proc" )
if(schemaDelimEndIndex == -1)
{
index = qualifiedProcedure.indexOf(namingSeparator);
} else
{
index = schemaDelimEndIndex;
}
if(index == -1)
{
csProcedure_ = JDUtilities.upperCaseIfNotQuoted(qualifiedProcedure);
// Currently don't handle correctly if more than one library in list.
csSchema_ = "";
}
else
{
csProcedure_ = JDUtilities.upperCaseIfNotQuoted(qualifiedProcedure.substring(index+1));
csSchema_ = JDUtilities.upperCaseIfNotQuoted(qualifiedProcedure.substring(0,index));
}
}
//@G4A New code ends
// Now we need to do some parsing based on the
// rest of the words. These are tests for the
// following certain phrases:
//
// CURRENT OF
// FOR FETCH ONLY
// FOR READ ONLY
// FOR UPDATE
// FROM (select from-clause)
//
boolean isSecondToken = true; // @F4A
while(tokenizer_.hasMoreTokens())
{
String token = tokenizer_.nextToken().toUpperCase();
if(isInsert_ && token.equals(SELECT_))
{
isSubSelect_ = true;
}
else if(token.equals(CURRENT_) && tokenizer_.hasMoreTokens() &&
tokenizer_.nextToken().equalsIgnoreCase(OF_))
{
isCurrentOf_ = true;
}
else if(token.equals(FOR_))
{
parseFor();
}
else if(isSelect_ && token.equals(FROM_) && selectTableNotSet_) //@K1C Added not set to check for subqueries
{
selectTableNotSet_ = false; //@K1A
if(tokenizer_.hasMoreTokens())
{
token = tokenizer_.nextToken(); //@F3C
if(!token.startsWith(LPAREN_))
{
// The "@G6A" code is trying to fix our parsing of the
// table-name in statements like
// SELECT * FROM collection."table with space" WHERE ....
// The tokenizer will break this up into tokens
// SELECT
// *
// FROM
// collection."table
// with
// space
// A customer reported a bug where we incorrectly re-formed
// the table name when doing an update row. The string we
// ended up with was.
// UPDATE collection."table SET column = value WHERE CURRENT OF cursor
// Note the fix is not to just slam tokens together, separating them by a space,
// until we find a token that ends with a quote. That won't fix the case
// where multiple spaces are between characters such as collection."a b".
// "a b" and "a b" are different tables. The fix is to go back to the
// original SQL statement, find the beginning of the collection/table name,
// then copy characters until finding the ending quote.
//if (token.indexOf('\"') >= 0) //@G6A
//{ //@G6A
// // find out if we already have the whole name by counting the quotes //@G6A
// int cnt = 0; //@G6A
// int ind = -1; //@G6A
// while ((ind = token.indexOf('\"', ind+1)) >= 0) { //@G6A
// cnt++; //@G6A
// } //@G6A
// // if there is an even number of quotes we already have an open //@G6A
// // and close so there is no need to look for another close //@G6A
// if (cnt % 2 == 0) { //@G6A
// selectTable_ = token; //@G6A
// } else { //@G6A
// // grab the rest of the token to the closing quote //@G6A
// String quotetok = ""; //@G6A
// if (tokenizer_.hasMoreTokens()) { //@G6A
// quotetok = tokenizer_.nextToken("\"") + "\""; //@G6A
// // grab the quote token from the end //@G6A
// tokenizer_.nextToken(" \t\n\r\f"); //@G6A
// } //@G6A
// selectTable_ = token + quotetok; //@G6A
// } //@G6A
//} //@G6A
//else //@G6A
// @C3A put the code to determine the naming separator down here too
String namingSeparator;
if((connection).getProperties().
getString(JDProperties.NAMING).equalsIgnoreCase("sql"))
{
namingSeparator = ".";
}
else
namingSeparator = "/";
// @C3A added the following block to handle case sensitive table and collection names
if(token.endsWith(namingSeparator))
{
StringBuffer table = new StringBuffer(token);
if(tokenizer_.hasMoreTokens())
table.append(tokenizer_.nextToken());
selectTable_ = table.toString();
}
else if(tokenizer_.hasMoreTokens() && tokenizer_.peekToken().equals(namingSeparator))
{
StringBuffer table = new StringBuffer(token);
table.append(tokenizer_.nextToken());
if(tokenizer_.hasMoreTokens())
table.append(tokenizer_.nextToken());
selectTable_ = table.toString();
}
else if(tokenizer_.hasMoreTokens() && tokenizer_.peekToken().startsWith(namingSeparator))
{
StringBuffer table = new StringBuffer(token);
table.append(tokenizer_.nextToken());
selectTable_ = table.toString();
}
else
{
selectTable_ = token; //@G6M
}
// @C3A end case sensitive table and collection name block
// If common table expressions are used, then sometimes the selectTable_ will end with a ).
// Go ahead and remove it. @WAA
int stLen = selectTable_.length();
if (selectTable_.charAt(stLen-1) == ')') {
selectTable_ = selectTable_.substring(0, stLen-1);
}
if(tokenizer_.hasMoreTokens())
{
token = tokenizer_.nextToken().toUpperCase();
if(token.equals(AS_) && tokenizer_.hasMoreTokens())
{
correlationName_ = tokenizer_.nextToken().toUpperCase();
}
else if(token.equals(FOR_))
{
parseFor();
}
}
}
}
}
else if(isSet_ && isSecondToken && token.equals(CONNECTION_)) // @F4A - This entire block.
{
nativeType_ = TYPE_CONNECT;
} else if (isSet_ && isSecondToken) {
if (token.equals(CURRENT_) ||
(token.equals("PATH")) ||
(token.equals("SESSION")) ||
(token.equals("SCHEMA")) ||
(token.equals("CURRENT_SCHEMA")) ||
(token.equals("COLLECTION"))) {
isSetSpecialRegister_ = true;
}
}
isSecondToken = false; // @F4A
}
// Based on all of the information that we
// have gathered up to this point, determine
// a few more tidbits.
boolean intermediate = (numberOfParameters_ > 0)
|| (isInsert_ && isSubSelect_)
|| (isCurrentOf_ && isUpdateOrDelete_);
isImmediatelyExecutable_ = ! (intermediate || isSelect_);
// @F8a Update package caching criteria to match ODBC and what is stated in
// @F8a our properties page.
// @F8a
// @F8d // @A1C
// @F8d // Changed the logic to determine isPackaged_ from the
// @F8d // "package criteria" property.
// @F8d if (packageCriteria.equalsIgnoreCase(JDProperties.PACKAGE_CRITERIA_DEFAULT))
// @F8d { // @A1A
// @F8d isPackaged_ = intermediate
// @F8d || (isSelect_ && ! isForFetchOrReadOnly_)
// @F8d || (isDeclare_);
// @F8d } // @A1A
// @F8d else
// @F8d { // @A1A
// @F8d isPackaged_ = (isInsert_ && isSubSelect_) // @A1A
// @F8d || (isCurrentOf_ && isUpdateOrDelete_) // @A1A
// @F8d || (isSelect_ && ! isForFetchOrReadOnly_) // @A1A
// @F8d || (isDeclare_); // @A1A
// @F8d } // @A1A
// 2011-11-29. If the statement has parameters, let it be stored in the package
// even if a update or delete.
isPackaged_ = ((numberOfParameters_ > 0) && !isCurrentOf_ ) // @F8a
|| (isInsert_ && isSubSelect_) // @F8a
|| (isSelect_ && isForUpdate_) // @F8a
|| (isDeclare_); // @F8a
// @F8a
if(packageCriteria.equalsIgnoreCase(JDProperties.PACKAGE_CRITERIA_SELECT)) // @F8a
{
// @F8a
isPackaged_ = isPackaged_ || isSelect_; // @F8a
} // @F8a
// If there is a return value parameter, strip if off now. @E1A
// The database does not understand these. @E1A
if(hasReturnValueParameter_)
{ // @E1A
int call = value_.toUpperCase().indexOf(CALL_); // @E1A
if(call != -1) // @E1A
{
value_ = value_.substring(call); // @E1A
}
} // @E1A
// Trim once and for all. @E1A
value_ = value_.trim(); // @E1A
tokenizer_ = null; //@mem make avail for GC now
}
//@H2A
/**
Indicates if the statement can be batched.
@return true if the statement can be batched;
false otherwise.
**/
boolean canBatch()
{
return canBeBatched_;
}
/**
Returns the number of parameters in the SQL statement.
@return Number of parameters.
**/
int countParameters()
{
return numberOfParameters_;
}
/**
Returns the correlation name for a SELECT statement.
@return The correlation name, or null if no correlation name
is specified, or this is not a SELECT statement.
**/
String getCorrelationName()
{
return correlationName_;
}
/**
Returns the native statement type.
@return Native type.
**/
int getNativeType()
{
return nativeType_;
}
//@G4A
/**
Returns the number of parameters for a statement.
@return The number of parameters parsed in this class, or 0
if no parameters were parsed.
**/
int getNumOfParameters()
{
return numberOfParameters_;
}
//@G4A
/**
Returns the procedure name name for a CALL stored procedure statement.
@return The procedure name, or null if no stored procedure name
is specified, or this is not a CALL statement.
**/
String getProcedure()
{
return csProcedure_;
}
//@G4A
/**
Returns the schema name for a CALL stored procedure statement.
@return The schema name, or null if no stored procedure name
is specified, or this is not a CALL statement.
**/
String getSchema()
{
return csSchema_;
}
/**
Returns the single table name for a SELECT statement.
@return The single table name, or null if multiple tables
were specified, or this is not a SELECT statement.
**/
String getSelectTable()
{
return selectTable_;
}
// @E1A
/**
Indicates if the SQL statement has a return value parameter.
@return true if the SQL statement has a return value parameter,
false otherwise.
**/
boolean hasReturnValueParameter() // @E1A
{ // @E1A
return hasReturnValueParameter_; // @E1A
} // @E1A
// @B1A
/**
Indicates if the statement initiates a
DRDA connection.
@return true if the statement initiates a
DRDA connection; false otherwise.
**/
boolean isDRDAConnect()
{
return isDRDAConnect_;
}
// @B1A
/**
Indicates if the statement closes a
DRDA connection.
@return true if the statement closes a
DRDA connection; false otherwise.
**/
boolean isDRDADisconnect()
{
return isDRDADisconnect_;
}
/**
Indicates if the statement contains a FOR FETCH
ONLY or FOR READ ONLY clause.
@return true if the statement contains a
FOR FETCH ONLY or FOR READ ONLY clause;
false otherwise.
**/
boolean isForFetchOnly()
{
return isForFetchOrReadOnly_;
}
/**
Indicates if the statement contains a FOR UPDATE
clause.
@return true if the statement contains a
FOR UPDATE clause; false otherwise.
**/
boolean isForUpdate()
{
return isForUpdate_;
}
/**
Indicates if the statement can be executed immediately
without doing a separate prepare and execute.
@return true if the statement can be executed
immediately executable; false otherwise.
**/
boolean isImmediatelyExecutable()
{
return isImmediatelyExecutable_;
}
/**
Indicates if this statement should be stored in
a package. This decision is based on characteristics
that make statements good candidates for being
stored in packages (those that will likely benefit
overall performance by being stored in a package).
This helps to reduce clutter in packages.
@return true if the statement should be stored
in a package; false otherwise.
**/
boolean isPackaged()
{
return isPackaged_;
}
/**
Indicates if the statement is a stored procedure call.
@return true if the statement is a stored
procedure call; false otherwise.
**/
boolean isProcedureCall()
{
return isCall_;
}
/**
Indicates if the statement a SELECT.
@return true if the statement is a SELECT;
false otherwise.
**/
boolean isSelect()
{
return isSelect_;
}
/**
Parses the token after FOR.
**/
private void parseFor()
{
if(tokenizer_.hasMoreTokens())
{
String token = tokenizer_.nextToken().toUpperCase();
if((token.equals(FETCH_)) || (token.equals(READ_)))
{
if(tokenizer_.hasMoreTokens() && tokenizer_.nextToken().equalsIgnoreCase(ONLY_))
{
isForFetchOrReadOnly_ = true;
}
}
else if(token.equals(UPDATE_))
{
isForUpdate_ = true;
}
}
}
// @G5 new method
/**
Sets the native statement type to one of the valid types.
If an invalid type is specified, no change will occur.
Valid types are:
- TYPE_UNDETERMINED
- TYPE_OTHER
- TYPE_UNDETERMINED
- TYPE_OTHER
- TYPE_SELECT
- TYPE_CALL
- TYPE_COMMIT
- TYPE_ROLLBACK
- TYPE_CONNECT
- TYPE_BLOCK_INSERT
@param type Native statement type.
**/
public void setNativeType (int type) // @G5A
{ // @G5A
nativeType_ = type; // @G5A
return; // @G5A
} // @G5A
// @GKA
public void setSelectFromInsert(boolean selectFromInsert){
selectFromInsert_ = selectFromInsert;
}
// @GKA
public boolean isSelectFromInsert()
{
return selectFromInsert_;
}
/**
Returns the SQL statement as a String. This will be
native SQL if conversion was requested.
@return The string, optionally native SQL.
**/
public String toString()
{
return value_; // @E1C
}
boolean getIsMetaDataCall() {
return isMetaDataCall_;
} /*@K5A*/
public boolean isSetSpecialRegister() {
return isSetSpecialRegister_;
}
}