1414import static org .firebirdsql .jaybird .parser .CharSequenceComparison .caseInsensitiveComparator ;
1515
1616/**
17- * Detects the type of statement, and - optionally - whether a DML statement has a {@code RETURNING} clause.
17+ * Detects the type of statement, and statement specific information like target object and {@code RETURNING} clause.
1818 * <p>
1919 * If the detected statement type is {@code UPDATE}, {@code DELETE}, {@code INSERT}, {@code UPDATE OR INSERT} and
2020 * {@code MERGE}, it identifies the affected table and - optionally - if a {@code RETURNING} clause is present
2121 * (delegated to a {@link ReturningClauseDetector}).
2222 * </p>
2323 * <p>
24- * The types of statements detected are informed by the needs of Jaybird, and may change between point releases.
24+ * If the detected statement is {@code EXECUTE PROCEDURE}, {@code CALL}, or - Callable Statement V2 only - a JDBC call
25+ * escape, it identifies the stored procedure.
26+ * </p>
27+ * <p>
28+ * The types of statements detected and other information are informed by the needs of Jaybird, and may change between
29+ * point releases.
2530 * </p>
2631 *
2732 * @author Mark Rotteveel
@@ -56,6 +61,11 @@ public final class StatementDetector implements TokenVisitor {
5661 nextAfterStart .put ("(" , new StateAfterStart (ParserState .SELECT , LocalStatementType .SELECT ));
5762 // Firebird 6.0+ USING ... DO; need to find end of the USING clause to detect actual statement type
5863 nextAfterStart .put ("USING" , new StateAfterStart (ParserState .FIND_USING_END , LocalStatementType .OTHER ));
64+ // Firebird 6.0+ CALL
65+ nextAfterStart .put ("CALL" , new StateAfterStart (ParserState .CALL , LocalStatementType .CALL ));
66+ // JDBC escape (search for call escape)
67+ // NOTE: JDBC call escape detection only applies to CallableStatement V2 handling
68+ nextAfterStart .put ("{" , new StateAfterStart (ParserState .JDBC_ESCAPE_START , LocalStatementType .OTHER ));
5969 NEXT_AFTER_START = unmodifiableMap (nextAfterStart );
6070 }
6171
@@ -115,16 +125,19 @@ public void visitToken(Token token, VisitorRegistrar visitorRegistrar) {
115125 visitorRegistrar .removeVisitor (this );
116126 } else {
117127 switch (parserState ) {
128+ case EXECUTE_PROCEDURE :
129+ case CALL :
130+ case JDBC_ESCAPE_CALL :
118131 case INSERT_INTO :
119132 case DML_TARGET :
120133 case DML_TARGET_FORWARD_TOKEN : {
121134 targetObjectExtractor = new ObjectReferenceExtractor ();
122- TokenVisitor registeredVisitor = targetObjectExtractor .onRemoveRegister (this );
123- visitorRegistrar .addVisitor (registeredVisitor );
135+ TokenVisitor newVisitor = targetObjectExtractor .onRemoveRegister (this );
136+ visitorRegistrar .addVisitor (newVisitor );
124137 visitorRegistrar .removeVisitor (this );
125138 if (parserState == ParserState .DML_TARGET_FORWARD_TOKEN ) {
126139 parserState = ParserState .DML_TARGET ;
127- registeredVisitor .visitToken (token , visitorRegistrar );
140+ newVisitor .visitToken (token , visitorRegistrar );
128141 }
129142 break ;
130143 }
@@ -152,10 +165,11 @@ public void visitToken(Token token, VisitorRegistrar visitorRegistrar) {
152165 @ Override
153166 public void complete (VisitorRegistrar visitorRegistrar ) {
154167 switch (parserState ) {
155- case INSERT_INTO -> updateStatementType (LocalStatementType .OTHER );
156- case DML_TARGET -> {
168+ // if parsing completes in these states, the statement is incomplete
169+ case JDBC_ESCAPE_CALL , INSERT_INTO -> updateStatementType (LocalStatementType .OTHER );
170+ // Handle DELETE FROM ... without WHERE, and EXECUTE PROCEDURE ... without arguments
171+ case EXECUTE_PROCEDURE , DML_TARGET -> {
157172 // TODO Maybe remove complete(..) and instead have the parser post an EOF token?
158- // Handle DELETE FROM ... without WHERE
159173 if (targetObject == null && !trySetTargetObject ()) {
160174 updateStatementType (LocalStatementType .OTHER );
161175 }
@@ -208,9 +222,6 @@ private enum ParserState {
208222 START {
209223 @ Override
210224 ParserState next (Token token , StatementDetector detector ) {
211- if (!(token instanceof ReservedToken || token instanceof ParenthesisOpen )) {
212- return forceOther (detector );
213- }
214225 StateAfterStart stateAfterStart =
215226 NEXT_AFTER_START .getOrDefault (token .textAsCharSequence (), INITIAL_OTHER );
216227 detector .updateStatementType (stateAfterStart .type );
@@ -228,7 +239,80 @@ ParserState next(Token token, StatementDetector detector) {
228239 return forceOther (detector );
229240 }
230241 },
231- EXECUTE_PROCEDURE (true ),
242+ EXECUTE_PROCEDURE {
243+ @ Override
244+ ParserState next (Token token , StatementDetector detector ) {
245+ if (detector .trySetTargetObject ()) {
246+ return EXEC_PROC_ARGS ;
247+ }
248+ return forceOther (detector );
249+ }
250+ },
251+ // TODO Final state for now; maybe merge with CALL_PROC_ARGS and/or JDBC_CALL_PROC_ARGS
252+ // TODO Do we need to parse and extract the arguments?
253+ EXEC_PROC_ARGS (true ),
254+ CALL {
255+ @ Override
256+ ParserState next (Token token , StatementDetector detector ) {
257+ if (detector .trySetTargetObject ()) {
258+ return CALL_PROC_ARGS ;
259+ }
260+ return forceOther (detector );
261+ }
262+ },
263+ // TODO Final state for now; maybe merge with EXEC_PROC_ARGS and/or JDBC_CALL_PROC_ARGS
264+ // TODO Do we need to parse and extract the arguments? Maybe for named support, or can we get that from prepare?
265+ CALL_PROC_ARGS (true ),
266+ // NOTE: JDBC call escape detection only applies to CallableStatement V2 handling
267+ JDBC_ESCAPE_START {
268+ @ Override
269+ ParserState next (Token token , StatementDetector detector ) {
270+ if (token instanceof PositionalParameterToken ) {
271+ return JDBC_ESCAPE_POSSIBLY_CALL_QM ;
272+ } else if (token .equalsIgnoreCase ("CALL" )) {
273+ // Call is not a reserved token in Firebird 5.0 and older
274+ detector .updateStatementType (LocalStatementType .JDBC_CALL_ESCAPE );
275+ return JDBC_ESCAPE_CALL ;
276+ }
277+ return forceOther (detector );
278+ }
279+ },
280+ // NOTE: JDBC call escape detection only applies to CallableStatement V2 handling
281+ JDBC_ESCAPE_POSSIBLY_CALL_QM {
282+ @ Override
283+ ParserState next (Token token , StatementDetector detector ) {
284+ if (token instanceof OperatorToken && token .equalsIgnoreCase ("=" )) {
285+ return JDBC_ESCAPE_POSSIBLY_CALL_EQ ;
286+ }
287+ return forceOther (detector );
288+ }
289+ },
290+ // NOTE: JDBC call escape detection only applies to CallableStatement V2 handling
291+ JDBC_ESCAPE_POSSIBLY_CALL_EQ {
292+ @ Override
293+ ParserState next (Token token , StatementDetector detector ) {
294+ if (token .equalsIgnoreCase ("CALL" )) {
295+ // Call is not a reserved token in Firebird 5.0 and older
296+ detector .updateStatementType (LocalStatementType .JDBC_CALL_RETURN_ESCAPE );
297+ return JDBC_ESCAPE_CALL ;
298+ }
299+ return forceOther (detector );
300+ }
301+ },
302+ // NOTE: JDBC call escape detection only applies to CallableStatement V2 handling
303+ JDBC_ESCAPE_CALL {
304+ @ Override
305+ ParserState next (Token token , StatementDetector detector ) {
306+ if (detector .trySetTargetObject ()) {
307+ return JDBC_CALL_PROC_ARGS ;
308+ }
309+ return forceOther (detector );
310+ }
311+ },
312+ // TODO Final state for now; maybe merge with CALL_PROC_ARGS and/or EXEC_PROC_ARGS
313+ // TODO We need to parse and extract the arguments
314+ // NOTE: JDBC call escape detection only applies to CallableStatement V2 handling
315+ JDBC_CALL_PROC_ARGS (true ),
232316 UPDATE {
233317 @ Override
234318 ParserState next (Token token , StatementDetector detector ) {
@@ -362,6 +446,12 @@ ParserState next(Token token, StatementDetector detector) {
362446 @ Override
363447 ParserState next (Token token , StatementDetector detector ) {
364448 // This is invoked after the end of USING ... DO has been found
449+ if (token instanceof CurlyBraceOpen ) {
450+ // This is probably a CALL escape, but the normal handling cannot apply as USING can change how
451+ // parameters are defined and handled
452+ detector .updateStatementType (LocalStatementType .JDBC_ESCAPE_AFTER_USING );
453+ return OTHER ;
454+ }
365455 return START .next (token , detector );
366456 }
367457 },
0 commit comments