org.firebirdsql.jdbc.FBEscapedCallParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaybird-jdk15 Show documentation
Show all versions of jaybird-jdk15 Show documentation
JDBC Driver for the Firebird RDBMS
/*
* Firebird Open Source J2ee connector - jdbc driver
*
* Distributable under LGPL license.
* You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* LGPL License for more details.
*
* This file was created by members of the firebird development team.
* All individual contributions remain the Copyright (C) of those
* individuals. Contributors to this file are either listed here or
* can be obtained from a CVS history command.
*
* All rights reserved.
*/
package org.firebirdsql.jdbc;
/**
* Parser for escaped procedure call.
*/
public class FBEscapedCallParser {
private static final int NORMAL_STATE = 1;
private static final int LITERAL_STATE = 2;
private static final int BRACE_STATE = 4;
private static final int CURLY_BRACE_STATE = 8;
private static final int SPACE_STATE = 16;
private static final int COMMA_STATE = 32;
private int state = NORMAL_STATE;
private int paramPosition;
private int paramCount;
private boolean isFirstOutParam;
private boolean isNameProcessed;
private boolean isExecuteWordProcessed;
private boolean isProcedureWordProcessed;
private boolean isCallWordProcessed;
private int openBraceCount;
private FBProcedureCall procedureCall;
private FBEscapedParser escapedParser;
public FBEscapedCallParser(int mode) {
this.escapedParser = new FBEscapedParser(mode);
}
/**
* Returns the current state.
*/
protected int getState() { return state; }
/**
* Sets the current state.
* @param state to enter.
* @throws java.lang.IllegalStateException
if the system
* cannot enter the desired state.
*/
protected void setState(int state) {
this.state = state;
}
/**
* Returns if the system is in state state
.
* @param state we're testing
* @return true
if the system is in state state
.
*/
protected boolean isInState(int state) { return this.state == state; }
/**
* Test the character to be the state switching character and switches
* the state if necessary.
* @param testChar character to test
*/
protected void switchState(char testChar) throws FBSQLParseException {
switch (testChar) {
case '\'' :
if (isInState(NORMAL_STATE))
setState(LITERAL_STATE);
else
if (isInState(LITERAL_STATE))
setState(NORMAL_STATE);
break;
case ' ' :
case '\t' :
case '\r' :
case '\n' :
case '\f' :
if (!isInState(LITERAL_STATE))
setState(SPACE_STATE);
break;
case ',' :
if (!isInState(LITERAL_STATE) && !isInState(BRACE_STATE))
setState(COMMA_STATE);
break;
case '(' :
case ')' :
if (isInState(LITERAL_STATE))
break;
setState(BRACE_STATE);
break;
case '{' :
case '}' :
if (!isInState(LITERAL_STATE))
setState(CURLY_BRACE_STATE);
break;
default :
if (!isInState(LITERAL_STATE) && !isInState(BRACE_STATE))
setState(NORMAL_STATE);
}
}
/**
* Clean the SQL statement. This method removes leading and trailing spaces
* and removes leading and trailing curly braces if any.
*
* @param sql SQL statement to clean up.
*
* @return cleaned up statement.
*
* @throws FBSQLParseException if cleanup resulted in empty statement.
*/
private String cleanUpCall(String sql) throws FBSQLParseException {
StringBuffer cleanupBuffer = new StringBuffer(sql);
// remove spaces at the beginning
while(cleanupBuffer.length() > 0 &&
Character.isSpaceChar(cleanupBuffer.charAt(0)))
cleanupBuffer.deleteCharAt(0);
// remove spaces at the end
while(cleanupBuffer.length() > 0 &&
Character.isSpaceChar(cleanupBuffer.charAt(cleanupBuffer.length() - 1)))
cleanupBuffer.deleteCharAt(cleanupBuffer.length() - 1);
if (cleanupBuffer.length() == 0)
throw new FBSQLParseException(
"Escaped call statement was empty.");
if (cleanupBuffer.charAt(0) == '{')
cleanupBuffer.deleteCharAt(0);
if (cleanupBuffer.charAt(cleanupBuffer.length() - 1) == '}')
cleanupBuffer.deleteCharAt(cleanupBuffer.length() - 1);
return cleanupBuffer.toString();
}
/**
* Check if either "call" keyword or "EXECUTE PROCEDURE" keyword processed.
*
* @return true
if either one or another keyword were processed.
*/
private boolean isCallKeywordProcessed() {
return isCallWordProcessed ||
(isExecuteWordProcessed && isProcedureWordProcessed);
}
/**
* Converts escaped parts in the passed SQL to native representation.
* @param sql to parse
*
* @return native form of the sql
.
*/
public FBProcedureCall parseCall(String sql) throws FBSQLException {
sql = cleanUpCall(sql);
procedureCall = new FBProcedureCall();
isExecuteWordProcessed = false;
isProcedureWordProcessed = false;
isCallWordProcessed = false;
isNameProcessed = false;
isFirstOutParam = false;
paramCount = 0;
paramPosition = 0;
setState(NORMAL_STATE);
char[] sqlbuff = sql.toCharArray();
StringBuffer buffer = new StringBuffer();
for(int i = 0; i < sqlbuff.length; i++) {
switchState(sqlbuff[i]);
if (isInState(NORMAL_STATE)) {
// if we have an equal sign, most likely {? = call ...}
// syntax is used (there's hardly any place for this sign
// in procedure parameters). but to be sure, we check if
// no brace is open and if buffer contains only '?'
if (sqlbuff[i] == '=') {
if (openBraceCount <= 0) {
String token = buffer.toString().trim();
if ("?".equals(token) && !isFirstOutParam && !isNameProcessed) {
FBProcedureParam param =
procedureCall.addParam(paramPosition, token);
paramCount++;
param.setIndex(paramCount);
isFirstOutParam = true;
paramPosition++;
buffer = new StringBuffer();
continue;
}
}
}
buffer.append(sqlbuff[i]);
} else
if (isInState(SPACE_STATE)) {
if (buffer.length() == 0) {
setState(NORMAL_STATE);
continue;
}
if (openBraceCount > 0) {
buffer.append(sqlbuff[i]);
setState(NORMAL_STATE);
continue;
}
String token = buffer.toString().trim();
// if procedure name was not yet processed, process
// the token; we look for the sequence EXECUTE PROCEDURE
// otherwise go into normal state to enable next transitions.
if (!isNameProcessed) {
boolean tokenProcessed = processToken(token);
if (tokenProcessed) {
buffer = new StringBuffer();
setState(NORMAL_STATE);
if (isNameProcessed){
// If we just found a name, fast-forward to the
// opening parenthesis, if there is one
int j = i;
while (j < sqlbuff.length - 1
&& Character.isWhitespace(sqlbuff[j])) j++;
if (sqlbuff[j] == '(')
i = j;
}
}
} else {
buffer.append(sqlbuff[i]);
setState(NORMAL_STATE);
}
} else
if (isInState(BRACE_STATE)) {
// if we have an opening brace and we already processed
// EXECUTE PROCEDURE words, but still do not have procedure
// name set, we can be sure that buffer contains procedure
// name.
boolean isProcedureName =
sqlbuff[i] == '(' &&
isCallKeywordProcessed() &&
!isNameProcessed;
if (isProcedureName) {
String token = buffer.toString().trim();
if ("".equals(token))
throw new FBSQLParseException(
"Procedure name is empty.");
procedureCall.setName(token);
isNameProcessed = true;
buffer = new StringBuffer();
} else {
buffer.append(sqlbuff[i]);
if (sqlbuff[i] == '(')
openBraceCount++;
else
openBraceCount--;
}
setState(NORMAL_STATE);
} else
if (isInState(CURLY_BRACE_STATE)) {
buffer.append(sqlbuff[i]);
setState(NORMAL_STATE);
} else
if (isInState(COMMA_STATE)) {
if (openBraceCount > 0) {
buffer.append(sqlbuff[i]);
continue;
}
String param = processParam(buffer.toString());
buffer = new StringBuffer();
FBProcedureParam callParam =
procedureCall.addParam(paramPosition, param);
if (callParam.isParam()) {
paramCount++;
callParam.setIndex(paramCount);
}
paramPosition++;
setState(NORMAL_STATE);
} else
if (isInState(LITERAL_STATE))
buffer.append(sqlbuff[i]);
}
if (buffer.length() == 0)
return procedureCall;
// remove spaces at the beginning and the end
while(Character.isSpaceChar(buffer.charAt(0)))
buffer.deleteCharAt(0);
while(Character.isSpaceChar(buffer.charAt(buffer.length() - 1)))
buffer.deleteCharAt(buffer.length() - 1);
// if buffer starts with '(', remove it,
// we do not want this thing to bother us
if (buffer.charAt(0) == '(')
buffer.deleteCharAt(0);
// if buffer ends with ')', remove it
// it should match an opening brace right after the procedure
// name, and we assume that all syntax check was already done.
if (buffer.charAt(buffer.length() - 1) == ')')
buffer.deleteCharAt(buffer.length() - 1);
// if there's something in the buffer, treat it as last param
if(null == procedureCall.getName() && !isNameProcessed) {
procedureCall.setName(buffer.toString());
} else {
FBProcedureParam callParam =
procedureCall.addParam(paramPosition, buffer.toString());
if (callParam.isParam()) {
paramCount++;
callParam.setIndex(paramCount);
}
}
return procedureCall;
}
/**
* Process token. This method detects procedure call keywords and sets
* appropriate flags. Also it detects procedure name and sets appropriate
* filed in the procedure call object.
*
* @param token token to process.
*
* @return true
if token was understood and processed.
*/
protected boolean processToken(String token) {
if ("EXECUTE".equalsIgnoreCase(token) &&
!isExecuteWordProcessed && !isProcedureWordProcessed && !isNameProcessed) {
isExecuteWordProcessed = true;
return true;
}
if ("PROCEDURE".equalsIgnoreCase(token) &&
isExecuteWordProcessed && !isProcedureWordProcessed && !isNameProcessed) {
isProcedureWordProcessed = true;
return true;
}
if ("call".equalsIgnoreCase(token) && !isCallWordProcessed && !isNameProcessed) {
isCallWordProcessed = true;
return true;
}
if ((isCallWordProcessed || (isExecuteWordProcessed && isProcedureWordProcessed)) && !isNameProcessed) {
procedureCall.setName(token);
isNameProcessed = true;
return true;
}
return false;
}
/**
* Pre-process parameter. This method checks if there is escaped call inside
* and converts it to the native one.
*
* @param param parameter to process.
*
* @return processed parameter.
*
* @throws FBSQLParseException if parameter cannot be correctly parsed.
*/
protected String processParam(String param) throws FBSQLException {
return escapedParser.parse(param);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy