Skip to content

Commit 72768e2

Browse files
committed
Rework statement detection to use object reference
Remove direct extraction of schema and table. This prepares for extraction of other objects like stored procedures, and for changes needed to work with packaged temporary tables.
1 parent e6eaf4e commit 72768e2

8 files changed

Lines changed: 226 additions & 284 deletions

File tree

src/main/org/firebirdsql/jaybird/parser/ObjectReferenceExtractor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.jspecify.annotations.Nullable;
88

99
import java.util.ArrayList;
10+
import java.util.Collection;
1011
import java.util.List;
1112
import java.util.Locale;
1213

@@ -116,6 +117,11 @@ public void complete(VisitorRegistrar visitorRegistrar) {
116117
}
117118
}
118119

120+
@Override
121+
public TokenVisitor onRemoveRegister(Collection<TokenVisitor> visitors) {
122+
return new RegisterOnRemoveTokenVisitor<>(this, visitors, true);
123+
}
124+
119125
/**
120126
* Obtains the object reference.
121127
*

src/main/org/firebirdsql/jaybird/parser/RegisterOnRemoveTokenVisitor.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
* ignore whitespace and comments, so this notified token is unlikely to be whitespace or a comment.
1414
* </p>
1515
* <p>
16+
* If {@link #complete(VisitorRegistrar)} is notified, the completion is forwarded to the other token visitors, but
17+
* those visitors are <em>not</em> registered (unless the decorated visitor removes itself on completion).
18+
* </p>
19+
* <p>
1620
* Recommended use is through {@link TokenVisitor#onRemoveRegister(Collection)} instead of creating this class directly.
1721
* </p>
1822
*
@@ -78,9 +82,16 @@ public void visitToken(Token token, VisitorRegistrar visitorRegistrar) {
7882

7983
@Override
8084
public void complete(VisitorRegistrar visitorRegistrar) {
85+
lastTokenSeen = DUMMY_NULL_TOKEN;
8186
currentRegistrar = visitorRegistrar;
8287
try {
8388
decoratedTokenVisitor.complete(this);
89+
for (TokenVisitor visitor : registerOnRemove) {
90+
try {
91+
visitor.complete(visitorRegistrar);
92+
} catch (Exception ignored) {
93+
}
94+
}
8495
} finally {
8596
currentRegistrar = VisitorRegistrar.noActionRegistrar();
8697
}
@@ -103,16 +114,16 @@ public void removeVisitor(TokenVisitor tokenVisitor) {
103114
// Remove this decorator instead of the decorated visitor
104115
currentRegistrar.removeVisitor(RegisterOnRemoveTokenVisitor.this);
105116
registerOnRemove.forEach(currentRegistrar::addVisitor);
106-
if (notifyLastToken) {
107-
for (TokenVisitor visitor : registerOnRemove) {
117+
if (notifyLastToken && lastTokenSeen != DUMMY_NULL_TOKEN) {
118+
registerOnRemove.forEach(visitor -> {
108119
try {
109120
// Notification is done with the real registrar
110121
visitor.visitToken(lastTokenSeen, currentRegistrar);
111122
} catch (RuntimeException e) {
112123
System.getLogger(getClass().getName()).log(System.Logger.Level.ERROR,
113124
"Ignored exception during token notification", e);
114125
}
115-
}
126+
});
116127
}
117128
}
118129

src/main/org/firebirdsql/jaybird/parser/StatementDetector.java

Lines changed: 56 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
// SPDX-License-Identifier: LGPL-2.1-or-later
33
package org.firebirdsql.jaybird.parser;
44

5+
import org.firebirdsql.jaybird.util.ObjectReference;
56
import org.firebirdsql.util.InternalApi;
67
import org.jspecify.annotations.Nullable;
78

89
import java.util.Map;
10+
import java.util.Optional;
911
import java.util.TreeMap;
1012

1113
import 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

Comments
 (0)