1- // SPDX-FileCopyrightText: Copyright 2021-2024 Mark Rotteveel
1+ // SPDX-FileCopyrightText: Copyright 2021-2025 Mark Rotteveel
22// SPDX-License-Identifier: LGPL-2.1-or-later
33package org .firebirdsql .jaybird .parser ;
44
1414 * Detects the type of statement, and - optionally - whether a DML statement has a {@code RETURNING} clause.
1515 * <p>
1616 * If the detected statement type is {@code UPDATE}, {@code DELETE}, {@code INSERT}, {@code UPDATE OR INSERT} and
17- * {@code MERGE}, it identifies the affected table and - optionally - whether or not a {@code RETURNING} clause is
18- * present (delegated to a {@link ReturningClauseDetector}).
17+ * {@code MERGE}, it identifies the affected table and - optionally - if a {@code RETURNING} clause is present
18+ * (delegated to a {@link ReturningClauseDetector}).
1919 * </p>
2020 * <p>
2121 * The types of statements detected are informed by the needs of Jaybird, and may change between point releases.
2727@ InternalApi
2828public final class StatementDetector implements TokenVisitor {
2929
30- // TODO Add schema support: identify (optional) schema
31-
3230 private static final StateAfterStart INITIAL_OTHER =
3331 new StateAfterStart (ParserState .OTHER , LocalStatementType .OTHER );
3432 private static final Map <CharSequence , StateAfterStart > NEXT_AFTER_START ;
@@ -55,6 +53,7 @@ public final class StatementDetector implements TokenVisitor {
5553 private final boolean detectReturning ;
5654 private LocalStatementType statementType = LocalStatementType .UNKNOWN ;
5755 private ParserState parserState = ParserState .START ;
56+ private Token schemaToken ;
5857 private Token tableNameToken ;
5958 private ReturningClauseDetector returningClauseDetector ;
6059
@@ -105,10 +104,13 @@ public void visitToken(Token token, VisitorRegistrar visitorRegistrar) {
105104 if (parserState .isFinalState ()) {
106105 // We're not interested anymore
107106 visitorRegistrar .removeVisitor (this );
108- } else if (parserState == ParserState .FIND_RETURNING ) {
109- // We're not interested anymore
110- visitorRegistrar .removeVisitor (this );
111- if (detectReturning ) {
107+ } else if (parserState == ParserState .FIND_RETURNING
108+ || parserState == ParserState .FIND_SCHEMA_SEPARATOR_OR_RETURNING ) {
109+ if (parserState == ParserState .FIND_RETURNING ) {
110+ // We're not interested anymore
111+ visitorRegistrar .removeVisitor (this );
112+ }
113+ if (detectReturning && returningClauseDetector == null ) {
112114 // Use ReturningClauseDetector to handle detection
113115 returningClauseDetector = new ReturningClauseDetector ();
114116 visitorRegistrar .addVisitor (returningClauseDetector );
@@ -124,8 +126,7 @@ public void complete(VisitorRegistrar visitorRegistrar) {
124126 }
125127
126128 public StatementIdentification toStatementIdentification () {
127- return new StatementIdentification (statementType , tableNameToken != null ? tableNameToken .text () : null ,
128- returningClauseDetected ());
129+ return new StatementIdentification (statementType , schemaToken , tableNameToken , returningClauseDetected ());
129130 }
130131
131132 boolean returningClauseDetected () {
@@ -139,6 +140,10 @@ public LocalStatementType getStatementType() {
139140 return statementType ;
140141 }
141142
143+ Token getSchemaToken () {
144+ return schemaToken ;
145+ }
146+
142147 Token getTableNameToken () {
143148 return tableNameToken ;
144149 }
@@ -151,6 +156,10 @@ private void updateStatementType(LocalStatementType statementType) {
151156 }
152157 }
153158
159+ private void setSchemaToken (Token schemaToken ) {
160+ this .schemaToken = schemaToken ;
161+ }
162+
154163 private void setTableNameToken (Token tableNameToken ) {
155164 this .tableNameToken = tableNameToken ;
156165 }
@@ -213,12 +222,46 @@ ParserState next(Token token, StatementDetector detector) {
213222 },
214223 // Shared by UPDATE, DELETE and MERGE
215224 DML_TARGET {
225+ @ Override
226+ ParserState next (Token token , StatementDetector detector ) {
227+ if (token .isValidIdentifier ()) {
228+ detector .setTableNameToken (token );
229+ return DML_SCHEMA_SEPARATOR_OR_POSSIBLE_ALIAS ;
230+ }
231+ return forceOther (detector );
232+ }
233+ },
234+ // Shared by UPDATE, DELETE and MERGE
235+ DML_SCHEMA_SEPARATOR_OR_POSSIBLE_ALIAS {
236+ @ Override
237+ ParserState next (Token token , StatementDetector detector ) {
238+ if (token instanceof PeriodToken ) {
239+ // What was detected as table, is actually the schema
240+ detector .setSchemaToken (detector .getTableNameToken ());
241+ detector .setTableNameToken (null );
242+ return DML_SCHEMA_QUALIFIED_TABLE_NAME ;
243+ } else if (token .isValidIdentifier ()) {
244+ // either alias or possibly returning clause
245+ return FIND_RETURNING ;
246+ } else if (token instanceof ReservedToken ) {
247+ if (token .equalsIgnoreCase ("AS" )) {
248+ return DML_ALIAS ;
249+ }
250+ return FIND_RETURNING ;
251+ }
252+ // Unexpected or invalid token at this point
253+ return forceOther (detector );
254+ }
255+ },
256+ // Shared by UPDATE, DELETE and MERGE
257+ DML_SCHEMA_QUALIFIED_TABLE_NAME {
216258 @ Override
217259 ParserState next (Token token , StatementDetector detector ) {
218260 if (token .isValidIdentifier ()) {
219261 detector .setTableNameToken (token );
220262 return DML_POSSIBLE_ALIAS ;
221263 }
264+ // Unexpected or invalid token at this point
222265 return forceOther (detector );
223266 }
224267 },
@@ -263,7 +306,7 @@ ParserState next(Token token, StatementDetector detector) {
263306 ParserState next (Token token , StatementDetector detector ) {
264307 if (token .isValidIdentifier ()) {
265308 detector .setTableNameToken (token );
266- return FIND_RETURNING ;
309+ return FIND_SCHEMA_SEPARATOR_OR_RETURNING ;
267310 }
268311 // Syntax error
269312 return forceOther (detector );
@@ -279,6 +322,29 @@ ParserState next(Token token, StatementDetector detector) {
279322 return forceOther (detector );
280323 }
281324 },
325+ FIND_SCHEMA_SEPARATOR_OR_RETURNING {
326+ @ Override
327+ ParserState next (Token token , StatementDetector detector ) {
328+ if (token instanceof PeriodToken ) {
329+ detector .setSchemaToken (detector .getTableNameToken ());
330+ detector .setTableNameToken (null );
331+ return FIND_SCHEMA_QUALIFIED_TABLE_OR_RETURNING ;
332+ } else {
333+ return FIND_RETURNING ;
334+ }
335+ }
336+ },
337+ FIND_SCHEMA_QUALIFIED_TABLE_OR_RETURNING {
338+ @ Override
339+ ParserState next (Token token , StatementDetector detector ) {
340+ if (token .isValidIdentifier ()) {
341+ detector .setTableNameToken (token );
342+ return FIND_RETURNING ;
343+ }
344+ // Syntax error
345+ return forceOther (detector );
346+ }
347+ },
282348 // finding itself is offloaded to ReturningClauseDetector
283349 FIND_RETURNING ,
284350 COMMIT_ROLLBACK {
0 commit comments