Skip to content

Commit fea51ec

Browse files
committed
fix: keep a dot in front of a number in an unsupported statement
captureRest() collapsed a dot into the token in front of it whenever the previous token ended with a dot or the current token was a dot, without looking at what followed. A dot which starts a number was therefore glued to its neighbour, and the captured token stream no longer read back the same way once it was rendered and parsed again: REPLACE( . 5 captured as REPLACE ( .5 REPLACE( INTO t VALUES (1, . 5) REPLACE ( INTO t VALUES ( 1 ,.5 ) REPLACE( x . 1 . 2 captured as REPLACE ( x.1.2 toString() of such a statement is not a fixpoint, and the token stream it prints is not what the input contained. The dot is now only used to collapse a qualified name when it is a qualifier. A dot which is followed by a number, and a number in front of a dot, keep the tokens apart. A qualified name still collapses: REPLACE( t . a . b captured as REPLACE ( t.a.b REPLACE( a . b captured as REPLACE ( a.b
1 parent 7123d25 commit fea51ec

2 files changed

Lines changed: 99 additions & 1 deletion

File tree

‎src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt‎

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25128,7 +25128,8 @@ List<String> captureRest() {
2512825128
int l = tokens.size();
2512925129
if( tok.kind == EOF || tok.kind == ST_SEMICOLON ) {
2513025130
break;
25131-
} else if ( l>0 && ( tok.image.equals(".") || tokens.get(l-1).endsWith(".")) ) {
25131+
} else if ( l>0 && !joinsNumberPart(tok) && joinsDot(tok, l) ) {
25132+
// collapse a qualified name such as a . b into the single a.b
2513225133
tokens.set(l-1, tokens.get(l-1) + tok.image);
2513325134
} else {
2513425135
tokens.add(tok.image);
@@ -25138,6 +25139,62 @@ List<String> captureRest() {
2513825139
return tokens;
2513925140
}
2514025141

25142+
/**
25143+
* Whether a dot which is about to be consumed joins the previous token to the
25144+
* one behind it.
25145+
*
25146+
* @param tok the token which is about to be consumed
25147+
* @param size the number of tokens captured so far
25148+
* @return whether the dot belongs to the previous token
25149+
*/
25150+
JAVACODE
25151+
boolean joinsDot(Token tok, int size) {
25152+
if (".".equals(tok.image)) {
25153+
// the dot closes the previous token, unless it opens a number as in a . 5
25154+
return !numberFollows();
25155+
}
25156+
return size > 0 && tok.kind != S_DOUBLE && tok.kind != S_LONG
25157+
&& numberPrecedes();
25158+
}
25159+
25160+
/**
25161+
* Whether the token behind the dot which is about to be consumed is a number,
25162+
* which makes the dot part of the number instead of a qualifier.
25163+
*
25164+
* @return whether a number follows the dot
25165+
*/
25166+
JAVACODE
25167+
boolean numberFollows() {
25168+
Token next = getToken(2);
25169+
return next != null && (next.kind == S_LONG || next.kind == S_DOUBLE);
25170+
}
25171+
25172+
/**
25173+
* Whether the previous token ends with a dot, which makes the token about to be
25174+
* consumed the part of a qualified name after that dot.
25175+
*
25176+
* @return whether a dot precedes the token
25177+
*/
25178+
JAVACODE
25179+
boolean numberPrecedes() {
25180+
Token previous = getToken(0);
25181+
return previous != null && previous.image.endsWith(".");
25182+
}
25183+
25184+
/**
25185+
* Whether a token has to stay separate because a {@code .} in it starts a
25186+
* number. The lexer reads {@code .5} and {@code 5.} as single doubles, so
25187+
* gluing such a token to its neighbour changes the token stream and the
25188+
* captured statement would no longer parse back.
25189+
*
25190+
* @param tok the token which is about to be consumed
25191+
* @return whether the token is a number
25192+
*/
25193+
JAVACODE
25194+
boolean joinsNumberPart(Token tok) {
25195+
return tok.kind == S_DOUBLE || tok.kind == S_LONG;
25196+
}
25197+
2514125198
/**
2514225199
* Captures opaque routine declarations through their terminating semicolon.
2514325200
* PostgreSQL AS string bodies can use ordinary, escape or dollar quotes.

‎src/test/java/net/sf/jsqlparser/statement/UnsupportedStatementTest.java‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,47 @@ public void testCaptureRestIssue1993() throws JSQLParserException {
126126
Assertions.assertEquals(4, statements.size());
127127
}
128128

129+
@Test
130+
void testCaptureRestKeepsADotInFrontOfANumber() throws JSQLParserException {
131+
// a dot which starts a number is not a qualifier, so it must not be
132+
// glued to the token in front of it: the captured tokens have to read
133+
// back the same way once they are rendered and parsed again
134+
for (String sqlStr : Arrays.asList("REPLACE( . 5",
135+
"REPLACE( INTO t VALUES (1, . 5)",
136+
"REPLACE( . . 5)",
137+
"REPLACE( 5 . 5)",
138+
"REPLACE( SELECT . 5",
139+
"REPLACE( x . 1 . 2")) {
140+
Statement statement = CCJSqlParserUtil.parse(sqlStr,
141+
parser -> parser.withUnsupportedStatements(true));
142+
assertInstanceOf(UnsupportedStatement.class, statement);
143+
144+
assertEquals(sqlStr.replaceAll("\\s+", ""), statement.toString().replaceAll("\\s+", ""));
145+
146+
String rendered = statement.toString();
147+
assertEquals(rendered, CCJSqlParserUtil.parse(rendered,
148+
parser -> parser.withUnsupportedStatements(true)).toString());
149+
}
150+
}
151+
152+
@Test
153+
void testCaptureRestStillCollapsesAQualifiedName() throws JSQLParserException {
154+
// the dot of a qualified name is a qualifier and is still collapsed
155+
Statement statement = CCJSqlParserUtil.parse("REPLACE( t . a . b",
156+
parser -> parser.withUnsupportedStatements(true));
157+
assertInstanceOf(UnsupportedStatement.class, statement);
158+
159+
assertEquals("REPLACE ( t.a.b", statement.toString());
160+
assertEquals(statement.toString(), CCJSqlParserUtil.parse(statement.toString(),
161+
parser -> parser.withUnsupportedStatements(true)).toString());
162+
163+
// and a quoted name still collapses to the single identifier
164+
Statements statements = CCJSqlParserUtil.parseStatements("Select 1; ALTER TABLE \"inter\".\"inter_user_rec\" \n"
165+
+ " OWNER TO \"postgres\"; select 2; select 3;",
166+
parser -> parser.withErrorRecovery(false));
167+
Assertions.assertEquals(4, statements.size());
168+
}
169+
129170
@Test
130171
void testFormerlyUnsupportedAlterIndex() throws JSQLParserException {
131172
String sqlStr =

0 commit comments

Comments
 (0)