in pgjdbc/src/main/java/org/postgresql/core/Parser.java [931:1152]
public static JdbcCallParseInfo modifyJdbcCall(String jdbcSql, boolean stdStrings,
int serverVersion, int protocolVersion, EscapeSyntaxCallMode escapeSyntaxCallMode) throws SQLException {
// Mini-parser for JDBC function-call syntax (only)
// TODO: Merge with escape processing (and parameter parsing?) so we only parse each query once.
// RE: frequently used statements are cached (see {@link org.postgresql.jdbc.PgConnection#borrowQuery}), so this "merge" is not that important.
String sql = jdbcSql;
boolean isFunction = false;
boolean outParamBeforeFunc = false;
int len = jdbcSql.length();
int state = 1;
boolean inQuotes = false;
boolean inEscape = false;
int startIndex = -1;
int endIndex = -1;
boolean syntaxError = false;
int i = 0;
while (i < len && !syntaxError) {
char ch = jdbcSql.charAt(i);
switch (state) {
case 1: // Looking for { at start of query
if (ch == '{') {
++i;
++state;
} else if (Character.isWhitespace(ch)) {
++i;
} else {
// Not function-call syntax. Skip the rest of the string.
i = len;
}
break;
case 2: // After {, looking for ? or =, skipping whitespace
if (ch == '?') {
outParamBeforeFunc =
isFunction = true; // { ? = call ... } -- function with one out parameter
++i;
++state;
} else if (ch == 'c' || ch == 'C') { // { call ... } -- proc with no out parameters
state += 3; // Don't increase 'i'
} else if (Character.isWhitespace(ch)) {
++i;
} else {
// "{ foo ...", doesn't make sense, complain.
syntaxError = true;
}
break;
case 3: // Looking for = after ?, skipping whitespace
if (ch == '=') {
++i;
++state;
} else if (Character.isWhitespace(ch)) {
++i;
} else {
syntaxError = true;
}
break;
case 4: // Looking for 'call' after '? =' skipping whitespace
if (ch == 'c' || ch == 'C') {
++state; // Don't increase 'i'.
} else if (Character.isWhitespace(ch)) {
++i;
} else {
syntaxError = true;
}
break;
case 5: // Should be at 'call ' either at start of string or after ?=
if ((ch == 'c' || ch == 'C') && i + 4 <= len && jdbcSql.substring(i, i + 4)
.equalsIgnoreCase("call")) {
isFunction = true;
i += 4;
++state;
} else if (Character.isWhitespace(ch)) {
++i;
} else {
syntaxError = true;
}
break;
case 6: // Looking for whitespace char after 'call'
if (Character.isWhitespace(ch)) {
// Ok, we found the start of the real call.
++i;
++state;
startIndex = i;
} else {
syntaxError = true;
}
break;
case 7: // In "body" of the query (after "{ [? =] call ")
if (ch == '\'') {
inQuotes = !inQuotes;
++i;
} else if (inQuotes && ch == '\\' && !stdStrings) {
// Backslash in string constant, skip next character.
i += 2;
} else if (!inQuotes && ch == '{') {
inEscape = !inEscape;
++i;
} else if (!inQuotes && ch == '}') {
if (!inEscape) {
// Should be end of string.
endIndex = i;
++i;
++state;
} else {
inEscape = false;
}
} else if (!inQuotes && ch == ';') {
syntaxError = true;
} else {
// Everything else is ok.
++i;
}
break;
case 8: // At trailing end of query, eating whitespace
if (Character.isWhitespace(ch)) {
++i;
} else {
syntaxError = true;
}
break;
default:
throw new IllegalStateException("somehow got into bad state " + state);
}
}
// We can only legally end in a couple of states here.
if (i == len && !syntaxError) {
if (state == 1) {
// Not an escaped syntax.
// Detect PostgreSQL native CALL.
// (OUT parameter registration, needed for stored procedures with INOUT arguments, will fail without this)
i = 0;
while (i < len && Character.isWhitespace(jdbcSql.charAt(i))) {
i++; // skip any preceding whitespace
}
if (i < len - 5) { // 5 == length of "call" + 1 whitespace
//Check for CALL followed by whitespace
char ch = jdbcSql.charAt(i);
if ((ch == 'c' || ch == 'C') && jdbcSql.substring(i, i + 4).equalsIgnoreCase("call")
&& Character.isWhitespace(jdbcSql.charAt(i + 4))) {
isFunction = true;
}
}
return new JdbcCallParseInfo(sql, isFunction);
}
if (state != 8) {
syntaxError = true; // Ran out of query while still parsing
}
}
if (syntaxError) {
throw new PSQLException(
GT.tr("Malformed function or procedure escape syntax at offset {0}.", i),
PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL);
}
String prefix;
String suffix;
if (escapeSyntaxCallMode == EscapeSyntaxCallMode.SELECT || serverVersion < 110000
|| (outParamBeforeFunc && escapeSyntaxCallMode == EscapeSyntaxCallMode.CALL_IF_NO_RETURN)) {
prefix = "select * from ";
suffix = " as result";
} else {
prefix = "call ";
suffix = "";
}
String s = jdbcSql.substring(startIndex, endIndex);
int prefixLength = prefix.length();
StringBuilder sb = new StringBuilder(prefixLength + jdbcSql.length() + suffix.length() + 10);
sb.append(prefix);
sb.append(s);
int opening = s.indexOf('(') + 1;
if (opening == 0) {
// here the function call has no parameters declaration eg : "{ ? = call pack_getValue}"
sb.append(outParamBeforeFunc ? "(?)" : "()");
} else if (outParamBeforeFunc) {
// move the single out parameter into the function call
// so that it can be treated like all other parameters
boolean needComma = false;
// the following loop will check if the function call has parameters
// eg "{ ? = call pack_getValue(?) }" vs "{ ? = call pack_getValue() }
for (int j = opening + prefixLength; j < sb.length(); j++) {
char c = sb.charAt(j);
if (c == ')') {
break;
}
if (!Character.isWhitespace(c)) {
needComma = true;
break;
}
}
// insert the return parameter as the first parameter of the function call
if (needComma) {
sb.insert(opening + prefixLength, "?,");
} else {
sb.insert(opening + prefixLength, "?");
}
}
if (!suffix.isEmpty()) {
sql = sb.append(suffix).toString();
} else {
sql = sb.toString();
}
return new JdbcCallParseInfo(sql, isFunction);
}