Skip to content

Commit b65db10

Browse files
committed
Polishing.
Reorder methods. Use CharObjectMap for operator character lookup instead of linear array iteration. Extract peek/has next token functionality into dedicated methods. Migrate assertions to AssertJ. Add benchmark. [#512][resolves #513] Signed-off-by: Mark Paluch <[email protected]>
1 parent 7f9b349 commit b65db10

File tree

4 files changed

+177
-95
lines changed

4 files changed

+177
-95
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.r2dbc.postgresql;
18+
19+
import org.junit.platform.commons.annotation.Testable;
20+
import org.openjdk.jmh.annotations.Benchmark;
21+
import org.openjdk.jmh.annotations.BenchmarkMode;
22+
import org.openjdk.jmh.annotations.Mode;
23+
import org.openjdk.jmh.annotations.OutputTimeUnit;
24+
import org.openjdk.jmh.infra.Blackhole;
25+
26+
import java.util.concurrent.TimeUnit;
27+
28+
/**
29+
* Benchmarks for {@link PostgresqlSqlParser}.
30+
*/
31+
@BenchmarkMode(Mode.Throughput)
32+
@OutputTimeUnit(TimeUnit.SECONDS)
33+
@Testable
34+
public class PostgresqlSqlParserBenchmarks extends BenchmarkSettings {
35+
36+
@Benchmark
37+
public void simpleStatement(Blackhole blackhole) {
38+
blackhole.consume(PostgresqlSqlParser.parse("SELECT * FROM FOO"));
39+
}
40+
41+
@Benchmark
42+
public void parametrizedStatement(Blackhole blackhole) {
43+
blackhole.consume(PostgresqlSqlParser.parse("SELECT * FROM FOO WHERE $2 = $1"));
44+
}
45+
46+
@Benchmark
47+
public void createOrReplaceFunction(Blackhole blackhole) {
48+
blackhole.consume(PostgresqlSqlParser.parse("CREATE OR REPLACE FUNCTION asterisks(n int)\n" +
49+
" RETURNS SETOF text\n" +
50+
" LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE\n" +
51+
"BEGIN ATOMIC\n" +
52+
"SELECT repeat('*', g) FROM generate_series (1, n) g; -- <-- Note this semicolon\n" +
53+
"END;"));
54+
}
55+
56+
}

src/main/java/io/r2dbc/postgresql/ParsedSql.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ public int getParameterCount() {
5050
}
5151

5252
public String getSql() {
53-
return sql;
53+
return this.sql;
5454
}
5555

5656
private static int getParameterCount(List<Statement> statements) {
5757
int sum = 0;
58-
for (Statement statement : statements){
58+
for (Statement statement : statements) {
5959
sum += statement.getParameterCount();
6060
}
6161
return sum;

src/main/java/io/r2dbc/postgresql/PostgresqlSqlParser.java

+78-51
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@
1616

1717
package io.r2dbc.postgresql;
1818

19+
import io.netty.util.collection.CharObjectHashMap;
20+
import io.netty.util.collection.CharObjectMap;
21+
1922
import java.util.ArrayList;
20-
import java.util.Arrays;
23+
import java.util.LinkedList;
2124
import java.util.List;
2225

2326
import static java.lang.Character.isWhitespace;
@@ -29,13 +32,79 @@
2932
*/
3033
class PostgresqlSqlParser {
3134

32-
private static final char[] SPECIAL_AND_OPERATOR_CHARS = {
33-
'+', '-', '*', '/', '<', '>', '=', '~', '!', '@', '#', '%', '^', '&', '|', '`', '?',
34-
'(', ')', '[', ']', ',', ';', ':', '*', '.', '\'', '"'
35-
};
35+
private static final CharObjectMap<Object> SPECIAL_AND_OPERATOR_CHARS = new CharObjectHashMap<>();
3636

3737
static {
38-
Arrays.sort(SPECIAL_AND_OPERATOR_CHARS);
38+
char[] specialCharsAndOperators = {'+', '-', '*', '/', '<', '>', '=', '~', '!', '@', '#', '%', '^', '&', '|', '`', '?',
39+
'(', ')', '[', ']', ',', ';', ':', '*', '.', '\'', '"'};
40+
41+
for (char c : specialCharsAndOperators) {
42+
SPECIAL_AND_OPERATOR_CHARS.put(c, new Object());
43+
}
44+
}
45+
46+
public static ParsedSql parse(String sql) {
47+
List<ParsedSql.Token> tokens = tokenize(sql);
48+
List<ParsedSql.Statement> statements = new ArrayList<>();
49+
LinkedList<Boolean> functionBodyList = null;
50+
51+
List<ParsedSql.Token> currentStatementTokens = new ArrayList<>(tokens.size());
52+
53+
for (int i = 0; i < tokens.size(); i++) {
54+
ParsedSql.Token current = tokens.get(i);
55+
currentStatementTokens.add(current);
56+
57+
if (current.getType() == ParsedSql.TokenType.DEFAULT) {
58+
String currentValue = current.getValue();
59+
60+
if (currentValue.equalsIgnoreCase("BEGIN")) {
61+
if (functionBodyList == null) {
62+
functionBodyList = new LinkedList<>();
63+
}
64+
if (hasNextToken(tokens, i) && peekNext(tokens, i).getValue().equalsIgnoreCase("ATOMIC")) {
65+
functionBodyList.add(true);
66+
} else {
67+
functionBodyList.add(false);
68+
}
69+
} else if (currentValue.equalsIgnoreCase("END") && functionBodyList != null && !functionBodyList.isEmpty()) {
70+
functionBodyList.removeLast();
71+
}
72+
} else if (current.getType().equals(ParsedSql.TokenType.STATEMENT_END)) {
73+
boolean inFunctionBody = false;
74+
75+
if (functionBodyList != null) {
76+
for (boolean b : functionBodyList) {
77+
inFunctionBody |= b;
78+
}
79+
}
80+
if (!inFunctionBody) {
81+
statements.add(new ParsedSql.Statement(currentStatementTokens));
82+
currentStatementTokens = new ArrayList<>();
83+
}
84+
}
85+
}
86+
87+
if (!currentStatementTokens.isEmpty()) {
88+
statements.add(new ParsedSql.Statement(currentStatementTokens));
89+
}
90+
91+
return new ParsedSql(sql, statements);
92+
}
93+
94+
private static ParsedSql.Token peekNext(List<ParsedSql.Token> tokens, int index) {
95+
return tokens.get(index + 1);
96+
}
97+
98+
private static boolean hasNextToken(List<ParsedSql.Token> tokens, int index) {
99+
return tokens.size() > index + 1;
100+
}
101+
102+
private static char peekNext(CharSequence sequence, int index) {
103+
return sequence.charAt(index + 1);
104+
}
105+
106+
private static boolean hasNextToken(CharSequence sequence, int index) {
107+
return sequence.length() > index + 1;
39108
}
40109

41110
private static List<ParsedSql.Token> tokenize(String sql) {
@@ -57,12 +126,12 @@ private static List<ParsedSql.Token> tokenize(String sql) {
57126
token = getQuotedIdentifierToken(sql, i);
58127
break;
59128
case '-': // Possible start of double-dash comment
60-
if ((i + 1) < sql.length() && sql.charAt(i + 1) == '-') {
129+
if (hasNextToken(sql, i) && peekNext(sql, i) == '-') {
61130
token = getCommentToLineEndToken(sql, i);
62131
}
63132
break;
64133
case '/': // Possible start of c-style comment
65-
if ((i + 1) < sql.length() && sql.charAt(i + 1) == '*') {
134+
if (hasNextToken(sql, i) && peekNext(sql, i) == '*') {
66135
token = getBlockCommentToken(sql, i);
67136
}
68137
break;
@@ -89,48 +158,6 @@ private static List<ParsedSql.Token> tokenize(String sql) {
89158
return tokens;
90159
}
91160

92-
public static ParsedSql parse(String sql) {
93-
List<ParsedSql.Token> tokens = tokenize(sql);
94-
List<ParsedSql.Statement> statements = new ArrayList<>();
95-
List<Boolean> functionBodyList = new ArrayList<>();
96-
97-
List<ParsedSql.Token> currentStatementTokens = new ArrayList<>();
98-
for (int i = 0; i < tokens.size(); i++) {
99-
ParsedSql.Token current = tokens.get(i);
100-
currentStatementTokens.add(current);
101-
102-
if (current.getType() == ParsedSql.TokenType.DEFAULT) {
103-
String currentValue = current.getValue();
104-
105-
if (currentValue.equalsIgnoreCase("BEGIN")) {
106-
if (i + 1 < tokens.size() && tokens.get(i + 1).getValue().equalsIgnoreCase("ATOMIC")) {
107-
functionBodyList.add(true);
108-
} else {
109-
functionBodyList.add(false);
110-
}
111-
} else if (currentValue.equalsIgnoreCase("END") && !functionBodyList.isEmpty()) {
112-
functionBodyList.remove(functionBodyList.size() - 1);
113-
}
114-
} else if (current.getType().equals(ParsedSql.TokenType.STATEMENT_END)) {
115-
boolean inFunctionBody = false;
116-
117-
for (boolean b : functionBodyList) {
118-
inFunctionBody |= b;
119-
}
120-
if (!inFunctionBody) {
121-
statements.add(new ParsedSql.Statement(currentStatementTokens));
122-
currentStatementTokens = new ArrayList<>();
123-
}
124-
}
125-
}
126-
127-
if (!currentStatementTokens.isEmpty()) {
128-
statements.add(new ParsedSql.Statement(currentStatementTokens));
129-
}
130-
131-
return new ParsedSql(sql, statements);
132-
}
133-
134161
private static ParsedSql.Token getDefaultToken(String sql, int beginIndex) {
135162
for (int i = beginIndex + 1; i < sql.length(); i++) {
136163
char c = sql.charAt(i);
@@ -142,7 +169,7 @@ private static ParsedSql.Token getDefaultToken(String sql, int beginIndex) {
142169
}
143170

144171
private static boolean isSpecialOrOperatorChar(char c) {
145-
return Arrays.binarySearch(SPECIAL_AND_OPERATOR_CHARS, c) >= 0;
172+
return SPECIAL_AND_OPERATOR_CHARS.containsKey(c);
146173
}
147174

148175
private static ParsedSql.Token getBlockCommentToken(String sql, int beginIndex) {

0 commit comments

Comments
 (0)