Skip to content

Commit e2a463e

Browse files
committed
feat: handle another corner case
1 parent ea4152f commit e2a463e

File tree

4 files changed

+69
-9
lines changed

4 files changed

+69
-9
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,10 @@ private boolean statementStartsWith(String sql, Iterable<String> checkStatements
480480
static final char SLASH = '/';
481481
static final char ASTERISK = '*';
482482
static final char DOLLAR = '$';
483+
static final char SPACE = ' ';
484+
static final char CLOSE_PARENTHESIS = ')';
485+
static final char COMMA = ',';
486+
static final char UNDERSCORE = '_';
483487

484488
/**
485489
* Removes comments from and trims the given sql statement using the dialect of this parser.
@@ -551,7 +555,7 @@ static int countOccurrencesOf(char c, String string) {
551555
* @return A boolean indicating whether the sql string has a Returning clause or not.
552556
*/
553557
@InternalApi
554-
abstract protected boolean checkReturningClauseInternal(String sql);
558+
protected abstract boolean checkReturningClauseInternal(String sql);
555559

556560
@InternalApi
557561
public boolean checkReturningClause(String sql) {

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/PostgreSQLStatementParser.java

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
@InternalApi
3232
public class PostgreSQLStatementParser extends AbstractStatementParser {
33-
private static final Pattern RETURNING_PATTERN = Pattern.compile("[ ')\"]returning[ '(\"]");
33+
private static final Pattern RETURNING_PATTERN = Pattern.compile("returning[ '(\"*]");
3434
private static final Pattern AS_RETURNING_PATTERN = Pattern.compile("[ ')\"]as returning[ '(\"]");
3535
private static final String RETURNING_STRING = "returning";
3636

@@ -144,7 +144,7 @@ String parseDollarQuotedString(String sql, int index) {
144144
if (c == DOLLAR) {
145145
return tag.toString();
146146
}
147-
if (!Character.isJavaIdentifierPart(c)) {
147+
if (!isValidIdentifierChar(c)) {
148148
break;
149149
}
150150
tag.append(c);
@@ -292,16 +292,55 @@ private void appendIfNotNull(
292292
}
293293
}
294294

295+
private boolean isValidIdentifierFirstChar(char c) {
296+
return Character.isLetter(c) || c == UNDERSCORE;
297+
}
298+
299+
private boolean isValidIdentifierChar(char c) {
300+
return isValidIdentifierFirstChar(c) || Character.isDigit(c) || c == DOLLAR;
301+
}
302+
303+
private boolean checkCharPrecedingReturning(char ch) {
304+
return (ch == SPACE)
305+
|| (ch == SINGLE_QUOTE)
306+
|| (ch == CLOSE_PARENTHESIS)
307+
|| (ch == DOUBLE_QUOTE)
308+
|| (ch == DOLLAR);
309+
}
310+
311+
private boolean checkCharPrecedingSubstrWithReturning(char ch) {
312+
return (ch == SPACE)
313+
|| (ch == SINGLE_QUOTE)
314+
|| (ch == CLOSE_PARENTHESIS)
315+
|| (ch == DOUBLE_QUOTE)
316+
|| (ch == COMMA);
317+
}
318+
295319
private boolean isReturning(String sql, int index) {
296320
// RETURNING is a reserved keyword in PG, but requires a
297321
// leading AS to be used as column label, to avoid ambiguity.
298322
// We thus check for cases which do not have a leading AS.
299323
// (https://www.postgresql.org/docs/current/sql-keywords-appendix.html)
300-
return (index >= 1)
301-
&& (index + 10 <= sql.length())
302-
&& RETURNING_PATTERN.matcher(sql.substring(index - 1, index + 10)).matches()
303-
&& !((index >= 4)
304-
&& AS_RETURNING_PATTERN.matcher(sql.substring(index - 4, index + 10)).matches());
324+
if (index >= 1) {
325+
if (((index + 10 <= sql.length())
326+
&& RETURNING_PATTERN.matcher(sql.substring(index, index + 10)).matches()
327+
&& !((index >= 4)
328+
&& AS_RETURNING_PATTERN.matcher(sql.substring(index - 4, index + 10)).matches()))) {
329+
if (checkCharPrecedingReturning(sql.charAt(index - 1))) {
330+
return true;
331+
}
332+
// Check for cases where returning clause is part of a substring which starts with an
333+
// invalid first character of an identifier.
334+
// For example,
335+
// insert into t select 2returning *;
336+
int ind = index - 1;
337+
while ((ind >= 0) && !checkCharPrecedingSubstrWithReturning(sql.charAt(ind))) {
338+
ind--;
339+
}
340+
return !isValidIdentifierFirstChar(sql.charAt(ind + 1));
341+
}
342+
}
343+
return false;
305344
}
306345

307346
@InternalApi

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerStatementParser.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
@InternalApi
3131
public class SpannerStatementParser extends AbstractStatementParser {
3232

33-
private static final Pattern THEN_RETURN_PATTERN = Pattern.compile("[ `')\"]then return[ `'(\"]");
33+
private static final Pattern THEN_RETURN_PATTERN =
34+
Pattern.compile("[ `')\"]then return[ *`'(\"]");
3435
private static final String THEN_STRING = "then";
3536
private static final String RETURN_STRING = "return";
3637

google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/StatementParserTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,10 @@ public void testGoogleSQLReturningClause() {
13601360
parser
13611361
.parse(Statement.of("insert into x (a,b) values (1,2)thenreturn*"))
13621362
.hasReturningClause());
1363+
assertTrue(
1364+
parser
1365+
.parse(Statement.of("insert into t(a) select \"x\"then return*"))
1366+
.hasReturningClause());
13631367
}
13641368

13651369
@Test
@@ -1455,6 +1459,18 @@ public void testPostgreSQLReturningClause() {
14551459
Statement.of(
14561460
"UPDATE x SET y = $returning$returning$returning$ WHERE z = 123 ReTuRnInG *"))
14571461
.hasReturningClause());
1462+
assertTrue(
1463+
parser.parse(Statement.of("insert into t1 select 1 returning*")).hasReturningClause());
1464+
assertTrue(
1465+
parser.parse(Statement.of("insert into t1 select 2returning*")).hasReturningClause());
1466+
assertTrue(
1467+
parser.parse(Statement.of("insert into t1 select 10e2returning*")).hasReturningClause());
1468+
assertFalse(
1469+
parser
1470+
.parse(Statement.of("insert into t1 select 'test''returning *'"))
1471+
.hasReturningClause());
1472+
assertTrue(
1473+
parser.parse(Statement.of("insert into t select 2,3returning*")).hasReturningClause());
14581474
}
14591475

14601476
private void assertUnclosedLiteral(String sql) {

0 commit comments

Comments
 (0)