Skip to content

Commit b5b1509

Browse files
committed
perf: further micro optimizations to parser
1 parent 63cf1e3 commit b5b1509

File tree

4 files changed

+63
-28
lines changed

4 files changed

+63
-28
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
2323
import com.google.common.base.Preconditions;
24+
import com.google.common.collect.ImmutableMap;
2425
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
2526
import java.io.Serializable;
2627
import java.util.Collections;
@@ -140,7 +141,7 @@ Builder handle(Value value) {
140141

141142
/** Creates a {@code Statement} with the given SQL text {@code sql}. */
142143
public static Statement of(String sql) {
143-
return newBuilder(sql).build();
144+
return new Statement(sql, ImmutableMap.of(), /*queryOptions=*/ null);
144145
}
145146

146147
/** Creates a new statement builder with the SQL text {@code sql}. */

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

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ ParsedStatement parse(Statement statement, QueryOptions defaultQueryOptions) {
513513
return parsedStatement.copy(statement, defaultQueryOptions);
514514
}
515515

516-
private ParsedStatement internalParse(Statement statement, QueryOptions defaultQueryOptions) {
516+
ParsedStatement internalParse(Statement statement, QueryOptions defaultQueryOptions) {
517517
StatementHintParser statementHintParser =
518518
new StatementHintParser(getDialect(), statement.getSql());
519519
ReadQueryUpdateTransactionOption[] optionsFromHints = EMPTY_OPTIONS;
@@ -523,16 +523,21 @@ private ParsedStatement internalParse(Statement statement, QueryOptions defaultQ
523523
statement.toBuilder().replace(statementHintParser.getSqlWithoutClientSideHints()).build();
524524
optionsFromHints = convertHintsToOptions(statementHintParser.getClientSideStatementHints());
525525
}
526+
// TODO: Qualify statements without removing comments first.
526527
String sql = removeCommentsAndTrim(statement.getSql());
527528
ClientSideStatementImpl client = parseClientSideStatement(sql);
528529
if (client != null) {
529530
return ParsedStatement.clientSideStatement(client, statement, sql);
530-
} else if (isQuery(sql)) {
531-
return ParsedStatement.query(statement, sql, defaultQueryOptions, optionsFromHints);
532-
} else if (isUpdateStatement(sql)) {
533-
return ParsedStatement.update(statement, sql, checkReturningClause(sql), optionsFromHints);
534-
} else if (isDdlStatement(sql)) {
535-
return ParsedStatement.ddl(statement, sql);
531+
} else {
532+
String sqlWithoutHints =
533+
!sql.isEmpty() && sql.charAt(0) == '@' ? removeStatementHint(sql) : sql;
534+
if (isQuery(sqlWithoutHints)) {
535+
return ParsedStatement.query(statement, sql, defaultQueryOptions, optionsFromHints);
536+
} else if (isUpdateStatement(sqlWithoutHints)) {
537+
return ParsedStatement.update(statement, sql, checkReturningClause(sql), optionsFromHints);
538+
} else if (isDdlStatement(sqlWithoutHints)) {
539+
return ParsedStatement.ddl(statement, sql);
540+
}
536541
}
537542
return ParsedStatement.unknown(statement, sql);
538543
}
@@ -935,7 +940,8 @@ int skipQuoted(
935940
appendIfNotNull(result, startQuote);
936941
appendIfNotNull(result, startQuote);
937942
}
938-
while (currentIndex < sql.length()) {
943+
int length = sql.length();
944+
while (currentIndex < length) {
939945
char currentChar = sql.charAt(currentIndex);
940946
if (currentChar == startQuote) {
941947
if (supportsDollarQuotedStrings() && currentChar == DOLLAR) {
@@ -946,7 +952,7 @@ int skipQuoted(
946952
return currentIndex + tag.length() + 2;
947953
}
948954
} else if (supportsEscapeQuoteWithQuote()
949-
&& sql.length() > currentIndex + 1
955+
&& length > currentIndex + 1
950956
&& sql.charAt(currentIndex + 1) == startQuote) {
951957
// This is an escaped quote (e.g. 'foo''bar')
952958
appendIfNotNull(result, currentChar);
@@ -955,7 +961,7 @@ int skipQuoted(
955961
continue;
956962
} else if (isTripleQuoted) {
957963
// Check if this is the end of the triple-quoted string.
958-
if (sql.length() > currentIndex + 2
964+
if (length > currentIndex + 2
959965
&& sql.charAt(currentIndex + 1) == startQuote
960966
&& sql.charAt(currentIndex + 2) == startQuote) {
961967
appendIfNotNull(result, currentChar);
@@ -969,7 +975,7 @@ int skipQuoted(
969975
}
970976
} else if (supportsBackslashEscape()
971977
&& currentChar == BACKSLASH
972-
&& sql.length() > currentIndex + 1
978+
&& length > currentIndex + 1
973979
&& sql.charAt(currentIndex + 1) == startQuote) {
974980
// This is an escaped quote (e.g. 'foo\'bar').
975981
// Note that in raw strings, the \ officially does not start an escape sequence, but the

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

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ String removeCommentsAndTrimInternal(String sql) {
125125
int multiLineCommentStartIdx = -1;
126126
StringBuilder res = new StringBuilder(sql.length());
127127
int index = 0;
128-
while (index < sql.length()) {
128+
int length = sql.length();
129+
while (index < length) {
129130
char c = sql.charAt(index);
130131
if (isInSingleLineComment) {
131132
if (c == '\n') {
@@ -134,34 +135,34 @@ String removeCommentsAndTrimInternal(String sql) {
134135
res.append(c);
135136
}
136137
} else if (multiLineCommentLevel > 0) {
137-
if (sql.length() > index + 1 && c == ASTERISK && sql.charAt(index + 1) == SLASH) {
138+
if (length > index + 1 && c == ASTERISK && sql.charAt(index + 1) == SLASH) {
138139
multiLineCommentLevel--;
139140
if (multiLineCommentLevel == 0) {
140-
if (!whitespaceBeforeOrAfterMultiLineComment && (sql.length() > index + 2)) {
141+
if (!whitespaceBeforeOrAfterMultiLineComment && (length > index + 2)) {
141142
whitespaceBeforeOrAfterMultiLineComment =
142143
Character.isWhitespace(sql.charAt(index + 2));
143144
}
144145
// If the multiline comment does not have any whitespace before or after it, and it is
145146
// neither at the start nor at the end of SQL string, append an extra space.
146147
if (!whitespaceBeforeOrAfterMultiLineComment
147148
&& (multiLineCommentStartIdx != 0)
148-
&& (index != sql.length() - 2)) {
149+
&& (index != length - 2)) {
149150
res.append(' ');
150151
}
151152
}
152153
index++;
153-
} else if (sql.length() > index + 1 && c == SLASH && sql.charAt(index + 1) == ASTERISK) {
154+
} else if (length > index + 1 && c == SLASH && sql.charAt(index + 1) == ASTERISK) {
154155
multiLineCommentLevel++;
155156
index++;
156157
}
157158
} else {
158159
// Check for -- which indicates the start of a single-line comment.
159-
if (sql.length() > index + 1 && c == HYPHEN && sql.charAt(index + 1) == HYPHEN) {
160+
if (length > index + 1 && c == HYPHEN && sql.charAt(index + 1) == HYPHEN) {
160161
// This is a single line comment.
161162
isInSingleLineComment = true;
162163
index += 2;
163164
continue;
164-
} else if (sql.length() > index + 1 && c == SLASH && sql.charAt(index + 1) == ASTERISK) {
165+
} else if (length > index + 1 && c == SLASH && sql.charAt(index + 1) == ASTERISK) {
165166
multiLineCommentLevel++;
166167
if (index >= 1) {
167168
whitespaceBeforeOrAfterMultiLineComment = Character.isWhitespace(sql.charAt(index - 1));

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

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
package com.google.cloud.spanner.connection;
1818

1919
import com.google.cloud.spanner.Dialect;
20+
import com.google.cloud.spanner.Statement;
21+
import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement;
22+
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
2023
import org.openjdk.jmh.annotations.Benchmark;
2124
import org.openjdk.jmh.annotations.Fork;
2225
import org.openjdk.jmh.annotations.Measurement;
@@ -27,28 +30,52 @@
2730
@Measurement(iterations = 5, time = 5)
2831
public class StatementParserBenchmark {
2932
private static final Dialect dialect = Dialect.POSTGRESQL;
30-
private static final AbstractStatementParser parser =
33+
private static final AbstractStatementParser PARSER =
3134
AbstractStatementParser.getInstance(dialect);
3235

33-
private static String longQueryText = generateLongQuery(100 * 1024); // 100kb
36+
private static final String LONG_QUERY_TEXT =
37+
generateLongStatement("SELECT * FROM foo WHERE 1", 100 * 1024); // 100kb
38+
39+
private static final String LONG_DML_TEXT =
40+
generateLongStatement("update foo set bar=1 WHERE 1", 100 * 1024); // 100kb
3441

3542
/** Generates a long SQL-looking string. */
36-
private static String generateLongQuery(int length) {
43+
private static String generateLongStatement(String prefix, int length) {
3744
StringBuilder sb = new StringBuilder(length + 50);
38-
sb.append("SELECT * FROM foo WHERE 1");
45+
sb.append(prefix);
3946
while (sb.length() < length) {
40-
sb.append(" OR abcdefghijklmnopqrstuvwxyz");
47+
sb.append(" OR abcdefghijklmnopqrstuvwxyz='abcdefghijklmnopqrstuvwxyz'");
4148
}
4249
return sb.toString();
4350
}
4451

4552
@Benchmark
46-
public boolean isQueryTest() {
47-
return parser.isQuery("CREATE TABLE FOO (ID INT64, NAME STRING(100)) PRIMARY KEY (ID)");
53+
public ParsedStatement isQueryTest() {
54+
return PARSER.internalParse(
55+
Statement.of("CREATE TABLE FOO (ID INT64, NAME STRING(100)) PRIMARY KEY (ID)"),
56+
QueryOptions.getDefaultInstance());
57+
}
58+
59+
@Benchmark
60+
public ParsedStatement longQueryTest() {
61+
return PARSER.internalParse(Statement.of(LONG_QUERY_TEXT), QueryOptions.getDefaultInstance());
4862
}
4963

5064
@Benchmark
51-
public boolean longQueryTest() {
52-
return parser.isQuery(longQueryText);
65+
public ParsedStatement longDmlTest() {
66+
return PARSER.internalParse(Statement.of(LONG_DML_TEXT), QueryOptions.getDefaultInstance());
67+
}
68+
69+
public static void main(String[] args) throws Exception {
70+
for (int i = 0; i < 100000; i++) {
71+
if (PARSER.internalParse(Statement.of(LONG_QUERY_TEXT), QueryOptions.getDefaultInstance())
72+
== null) {
73+
throw new AssertionError();
74+
}
75+
if (PARSER.internalParse(Statement.of(LONG_DML_TEXT), QueryOptions.getDefaultInstance())
76+
== null) {
77+
throw new AssertionError();
78+
}
79+
}
5380
}
5481
}

0 commit comments

Comments
 (0)