22// SPDX-License-Identifier: LGPL-2.1-or-later
33package org .firebirdsql .jaybird .parser ;
44
5+ import org .firebirdsql .jaybird .util .ObjectReference ;
56import org .firebirdsql .util .InternalApi ;
67import org .jspecify .annotations .Nullable ;
78
89import java .util .Map ;
10+ import java .util .Optional ;
911import java .util .TreeMap ;
1012
1113import static java .util .Collections .unmodifiableMap ;
@@ -60,8 +62,8 @@ public final class StatementDetector implements TokenVisitor {
6062 private final boolean detectReturning ;
6163 private LocalStatementType statementType = LocalStatementType .UNKNOWN ;
6264 private ParserState parserState = ParserState .START ;
63- private @ Nullable Token schemaToken ;
64- private @ Nullable Token tableNameToken ;
65+ private @ Nullable ObjectReference targetObject ;
66+ private @ Nullable ObjectReferenceExtractor targetObjectExtractor ;
6567 private @ Nullable ReturningClauseDetector returningClauseDetector ;
6668
6769 /**
@@ -113,11 +115,22 @@ public void visitToken(Token token, VisitorRegistrar visitorRegistrar) {
113115 visitorRegistrar .removeVisitor (this );
114116 } else {
115117 switch (parserState ) {
118+ case INSERT_INTO :
119+ case DML_TARGET :
120+ case DML_TARGET_FORWARD_TOKEN : {
121+ targetObjectExtractor = new ObjectReferenceExtractor ();
122+ TokenVisitor registeredVisitor = targetObjectExtractor .onRemoveRegister (this );
123+ visitorRegistrar .addVisitor (registeredVisitor );
124+ visitorRegistrar .removeVisitor (this );
125+ if (parserState == ParserState .DML_TARGET_FORWARD_TOKEN ) {
126+ parserState = ParserState .DML_TARGET ;
127+ registeredVisitor .visitToken (token , visitorRegistrar );
128+ }
129+ break ;
130+ }
116131 case FIND_RETURNING :
117132 // We're not interested any more
118133 visitorRegistrar .removeVisitor (this );
119- // intentional fallthrough
120- case FIND_SCHEMA_SEPARATOR_OR_RETURNING :
121134 if (detectReturning && returningClauseDetector == null ) {
122135 // Use ReturningClauseDetector to handle detection
123136 returningClauseDetector = new ReturningClauseDetector ();
@@ -136,8 +149,22 @@ public void visitToken(Token token, VisitorRegistrar visitorRegistrar) {
136149 }
137150 }
138151
152+ @ Override
153+ public void complete (VisitorRegistrar visitorRegistrar ) {
154+ switch (parserState ) {
155+ case INSERT_INTO -> updateStatementType (LocalStatementType .OTHER );
156+ case DML_TARGET -> {
157+ // TODO Maybe remove complete(..) and instead have the parser post an EOF token?
158+ // Handle DELETE FROM ... without WHERE
159+ if (targetObject == null && !trySetTargetObject ()) {
160+ updateStatementType (LocalStatementType .OTHER );
161+ }
162+ }
163+ }
164+ }
165+
139166 public StatementIdentification toStatementIdentification () {
140- return new StatementIdentification (statementType , schemaToken , tableNameToken , returningClauseDetected ());
167+ return new StatementIdentification (statementType , targetObject , returningClauseDetected ());
141168 }
142169
143170 boolean returningClauseDetected () {
@@ -151,31 +178,32 @@ public LocalStatementType getStatementType() {
151178 return statementType ;
152179 }
153180
154- @ Nullable Token getSchemaToken () {
155- return schemaToken ;
181+ Optional <ObjectReference > getTargetObject () {
182+ return Optional .ofNullable (targetObject );
183+ }
184+
185+ void setTargetObject (@ Nullable ObjectReference targetObject ) {
186+ this .targetObject = targetObject ;
156187 }
157188
158- @ Nullable Token getTableNameToken () {
159- return tableNameToken ;
189+ boolean trySetTargetObject () {
190+ if (targetObjectExtractor == null ) return false ;
191+ try {
192+ setTargetObject (targetObjectExtractor .toObjectReference ());
193+ return true ;
194+ } catch (IllegalStateException ignored ) {
195+ return false ;
196+ }
160197 }
161198
162199 private void updateStatementType (LocalStatementType statementType ) {
163200 this .statementType = statementType ;
164201 if (statementType == LocalStatementType .OTHER ) {
165- // clear any previously set schema and table name
166- setSchemaToken (null );
167- setTableNameToken (null );
202+ // clear any previously set target object
203+ setTargetObject (null );
168204 }
169205 }
170206
171- private void setSchemaToken (@ Nullable Token schemaToken ) {
172- this .schemaToken = schemaToken ;
173- }
174-
175- private void setTableNameToken (@ Nullable Token tableNameToken ) {
176- this .tableNameToken = tableNameToken ;
177- }
178-
179207 private enum ParserState {
180208 START {
181209 @ Override
@@ -208,7 +236,7 @@ ParserState next(Token token, StatementDetector detector) {
208236 detector .updateStatementType (LocalStatementType .UNKNOWN );
209237 return POSSIBLY_UPDATE_OR_INSERT ;
210238 } else {
211- return DML_TARGET . next ( token , detector ) ;
239+ return DML_TARGET_FORWARD_TOKEN ;
212240 }
213241 }
214242 },
@@ -233,50 +261,18 @@ ParserState next(Token token, StatementDetector detector) {
233261 }
234262 },
235263 // Shared by UPDATE, DELETE and MERGE
264+ // Finding the DML target itself is offloaded to ObjectReferenceExtractor
236265 DML_TARGET {
237266 @ Override
238267 ParserState next (Token token , StatementDetector detector ) {
239- if (token .isValidIdentifier ()) {
240- detector .setTableNameToken (token );
241- return DML_SCHEMA_SEPARATOR_OR_POSSIBLE_ALIAS ;
268+ if (detector .trySetTargetObject ()) {
269+ return DML_POSSIBLE_ALIAS .next (token , detector );
242270 }
243271 return forceOther (detector );
244272 }
245273 },
246- // Shared by UPDATE, DELETE and MERGE
247- DML_SCHEMA_SEPARATOR_OR_POSSIBLE_ALIAS {
248- @ Override
249- ParserState next (Token token , StatementDetector detector ) {
250- if (token instanceof PeriodToken ) {
251- // What was detected as table, is actually the schema
252- detector .setSchemaToken (detector .getTableNameToken ());
253- detector .setTableNameToken (null );
254- return DML_SCHEMA_QUALIFIED_TABLE_NAME ;
255- } else if (token .isValidIdentifier ()) {
256- // either alias or possibly returning clause
257- return FIND_RETURNING ;
258- } else if (token instanceof ReservedToken ) {
259- if (token .equalsIgnoreCase ("AS" )) {
260- return DML_ALIAS ;
261- }
262- return FIND_RETURNING ;
263- }
264- // Unexpected or invalid token at this point
265- return forceOther (detector );
266- }
267- },
268- // Shared by UPDATE, DELETE and MERGE
269- DML_SCHEMA_QUALIFIED_TABLE_NAME {
270- @ Override
271- ParserState next (Token token , StatementDetector detector ) {
272- if (token .isValidIdentifier ()) {
273- detector .setTableNameToken (token );
274- return DML_POSSIBLE_ALIAS ;
275- }
276- // Unexpected or invalid token at this point
277- return forceOther (detector );
278- }
279- },
274+ // For UPDATE to signal the current token must be forwarded to the object reference extractor
275+ DML_TARGET_FORWARD_TOKEN ,
280276 // Shared by UPDATE, DELETE and MERGE
281277 DML_POSSIBLE_ALIAS {
282278 @ Override
@@ -316,11 +312,9 @@ ParserState next(Token token, StatementDetector detector) {
316312 INSERT_INTO {
317313 @ Override
318314 ParserState next (Token token , StatementDetector detector ) {
319- if (token .isValidIdentifier ()) {
320- detector .setTableNameToken (token );
321- return FIND_SCHEMA_SEPARATOR_OR_RETURNING ;
315+ if (detector .trySetTargetObject ()) {
316+ return FIND_RETURNING ;
322317 }
323- // Syntax error
324318 return forceOther (detector );
325319 }
326320 },
@@ -334,29 +328,6 @@ ParserState next(Token token, StatementDetector detector) {
334328 return forceOther (detector );
335329 }
336330 },
337- FIND_SCHEMA_SEPARATOR_OR_RETURNING {
338- @ Override
339- ParserState next (Token token , StatementDetector detector ) {
340- if (token instanceof PeriodToken ) {
341- detector .setSchemaToken (detector .getTableNameToken ());
342- detector .setTableNameToken (null );
343- return FIND_SCHEMA_QUALIFIED_TABLE_OR_RETURNING ;
344- } else {
345- return FIND_RETURNING ;
346- }
347- }
348- },
349- FIND_SCHEMA_QUALIFIED_TABLE_OR_RETURNING {
350- @ Override
351- ParserState next (Token token , StatementDetector detector ) {
352- if (token .isValidIdentifier ()) {
353- detector .setTableNameToken (token );
354- return FIND_RETURNING ;
355- }
356- // Syntax error
357- return forceOther (detector );
358- }
359- },
360331 // finding itself is offloaded to ReturningClauseDetector
361332 FIND_RETURNING ,
362333 COMMIT_ROLLBACK {
0 commit comments