Skip to content

Commit 1a85d5f

Browse files
committed
Polishing.
Move SQL lexer to main package to not expose public API. Add this keyword when accessing fields. [resolves #468] Signed-off-by: Mark Paluch <mpaluch@vmware.com>
1 parent 376f283 commit 1a85d5f

File tree

9 files changed

+371
-410
lines changed

9 files changed

+371
-410
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package io.r2dbc.postgresql;
1818

1919
import io.r2dbc.postgresql.util.Assert;
20-
import io.r2dbc.postgresql.util.sql.BasicPostgresqlSqlLexer;
2120
import io.r2dbc.spi.Batch;
2221
import reactor.core.publisher.Flux;
2322

@@ -41,7 +40,7 @@ final class PostgresqlBatch implements io.r2dbc.postgresql.api.PostgresqlBatch {
4140
public PostgresqlBatch add(String sql) {
4241
Assert.requireNonNull(sql, "sql must not be null");
4342

44-
if (!(BasicPostgresqlSqlLexer.tokenize(sql).getParameterCount() == 0)) {
43+
if (!(PostgresqlSqlLexer.tokenize(sql).getParameterCount() == 0)) {
4544
throw new IllegalArgumentException(String.format("Statement '%s' is not supported. This is often due to the presence of parameters.", sql));
4645
}
4746

src/main/java/io/r2dbc/postgresql/util/sql/BasicPostgresqlSqlLexer.java renamed to src/main/java/io/r2dbc/postgresql/PostgresqlSqlLexer.java

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,20 @@
1414
* limitations under the License.
1515
*/
1616

17-
package io.r2dbc.postgresql.util.sql;
17+
package io.r2dbc.postgresql;
1818

1919
import java.util.ArrayList;
2020
import java.util.Arrays;
2121
import java.util.List;
2222

23-
import static java.lang.Character.isDigit;
2423
import static java.lang.Character.isWhitespace;
25-
import static java.lang.Character.toLowerCase;
2624

27-
public class BasicPostgresqlSqlLexer {
25+
/**
26+
* Utility to tokenize Postgres SQL statements.
27+
*
28+
* @since 0.9
29+
*/
30+
class PostgresqlSqlLexer {
2831

2932
private static final char[] SPECIAL_AND_OPERATOR_CHARS = {
3033
'+', '-', '*', '/', '<', '>', '=', '~', '!', '@', '#', '%', '^', '&', '|', '`', '?',
@@ -36,14 +39,14 @@ public class BasicPostgresqlSqlLexer {
3639
}
3740

3841
public static TokenizedSql tokenize(String sql) {
39-
List<Token> tokens = new ArrayList<>();
40-
List<TokenizedStatement> statements = new ArrayList<>();
42+
List<TokenizedSql.Token> tokens = new ArrayList<>();
43+
List<TokenizedSql.TokenizedStatement> statements = new ArrayList<>();
4144

4245
int statementStartIndex = 0;
4346
int i = 0;
4447
while (i < sql.length()) {
4548
char c = sql.charAt(i);
46-
Token token = null;
49+
TokenizedSql.Token token = null;
4750

4851
if (isWhitespace(c)) {
4952
i++;
@@ -70,25 +73,25 @@ public static TokenizedSql tokenize(String sql) {
7073
token = getParameterOrDollarQuoteToken(sql, i);
7174
break;
7275
case ';':
73-
token = new Token(TokenType.STATEMENT_END, ";");
76+
token = new TokenizedSql.Token(TokenizedSql.TokenType.STATEMENT_END, ";");
7477
break;
7578
default:
7679
break;
7780
}
7881
if (token == null) {
7982
if (isSpecialOrOperatorChar(c)) {
80-
token = new Token(TokenType.SPECIAL_OR_OPERATOR, Character.toString(c));//getSpecialOrOperatorToken(sql, i);
83+
token = new TokenizedSql.Token(TokenizedSql.TokenType.SPECIAL_OR_OPERATOR, Character.toString(c));//getSpecialOrOperatorToken(sql, i);
8184
} else {
8285
token = getDefaultToken(sql, i);
8386
}
8487
}
8588

8689
i += token.getValue().length();
8790

88-
if (token.getType() == TokenType.STATEMENT_END) {
91+
if (token.getType() == TokenizedSql.TokenType.STATEMENT_END) {
8992

9093
tokens.add(token);
91-
statements.add(new TokenizedStatement(sql.substring(statementStartIndex, i), tokens));
94+
statements.add(new TokenizedSql.TokenizedStatement(sql.substring(statementStartIndex, i), tokens));
9295

9396
tokens = new ArrayList<>();
9497
statementStartIndex = i + 1;
@@ -98,27 +101,27 @@ public static TokenizedSql tokenize(String sql) {
98101
}
99102
// If tokens is not empty, implicit statement end
100103
if (!tokens.isEmpty()) {
101-
statements.add(new TokenizedStatement(sql.substring(statementStartIndex), tokens));
104+
statements.add(new TokenizedSql.TokenizedStatement(sql.substring(statementStartIndex), tokens));
102105
}
103106

104107
return new TokenizedSql(sql, statements);
105108
}
106109

107-
private static Token getDefaultToken(String sql, int beginIndex) {
110+
private static TokenizedSql.Token getDefaultToken(String sql, int beginIndex) {
108111
for (int i = beginIndex + 1; i < sql.length(); i++) {
109112
char c = sql.charAt(i);
110113
if (Character.isWhitespace(c) || isSpecialOrOperatorChar(c)) {
111-
return new Token(TokenType.DEFAULT, sql.substring(beginIndex, i));
114+
return new TokenizedSql.Token(TokenizedSql.TokenType.DEFAULT, sql.substring(beginIndex, i));
112115
}
113116
}
114-
return new Token(TokenType.DEFAULT, sql.substring(beginIndex));
117+
return new TokenizedSql.Token(TokenizedSql.TokenType.DEFAULT, sql.substring(beginIndex));
115118
}
116119

117120
private static boolean isSpecialOrOperatorChar(char c) {
118121
return Arrays.binarySearch(SPECIAL_AND_OPERATOR_CHARS, c) >= 0;
119122
}
120123

121-
private static Token getBlockCommentToken(String sql, int beginIndex) {
124+
private static TokenizedSql.Token getBlockCommentToken(String sql, int beginIndex) {
122125
int depth = 1;
123126
for (int i = beginIndex + 2; i < (sql.length() - 1); i++) {
124127
String biGraph = sql.substring(i, i + 2);
@@ -131,78 +134,78 @@ private static Token getBlockCommentToken(String sql, int beginIndex) {
131134
i++;
132135
}
133136
if (depth == 0) {
134-
return new Token(TokenType.COMMENT, sql.substring(beginIndex, i + 1));
137+
return new TokenizedSql.Token(TokenizedSql.TokenType.COMMENT, sql.substring(beginIndex, i + 1));
135138
}
136139
}
137140
throw new IllegalArgumentException("Sql cannot be parsed: unclosed block comment (comment opened at index " + beginIndex + ") in statement: " + sql);
138141
}
139142

140-
private static Token getCommentToLineEndToken(String sql, int beginIndex) {
143+
private static TokenizedSql.Token getCommentToLineEndToken(String sql, int beginIndex) {
141144
int lineEnding = sql.indexOf('\n', beginIndex);
142145
if (lineEnding == -1) {
143-
return new Token(TokenType.COMMENT, sql.substring(beginIndex));
146+
return new TokenizedSql.Token(TokenizedSql.TokenType.COMMENT, sql.substring(beginIndex));
144147
} else {
145-
return new Token(TokenType.COMMENT, sql.substring(beginIndex, lineEnding));
148+
return new TokenizedSql.Token(TokenizedSql.TokenType.COMMENT, sql.substring(beginIndex, lineEnding));
146149
}
147150
}
148151

149-
private static Token getDollarQuoteToken(String sql, String tag, int beginIndex) {
152+
private static TokenizedSql.Token getDollarQuoteToken(String sql, String tag, int beginIndex) {
150153
int nextQuote = sql.indexOf(tag, beginIndex + tag.length());
151154
if (nextQuote == -1) {
152155
throw new IllegalArgumentException("Sql cannot be parsed: unclosed quote (quote opened at index " + beginIndex + ") in statement: " + sql);
153156
} else {
154-
return new Token(TokenType.STRING_CONSTANT, sql.substring(beginIndex, nextQuote + tag.length()));
157+
return new TokenizedSql.Token(TokenizedSql.TokenType.STRING_CONSTANT, sql.substring(beginIndex, nextQuote + tag.length()));
155158
}
156159
}
157160

158-
private static Token getParameterToken(String sql, int beginIndex) {
161+
private static TokenizedSql.Token getParameterToken(String sql, int beginIndex) {
159162
for (int i = beginIndex + 1; i < sql.length(); i++) {
160163
char c = sql.charAt(i);
161164
if (isWhitespace(c) || isSpecialOrOperatorChar(c)) {
162-
return new Token(TokenType.PARAMETER, sql.substring(beginIndex, i));
165+
return new TokenizedSql.Token(TokenizedSql.TokenType.PARAMETER, sql.substring(beginIndex, i));
163166
}
164167
if (!isAsciiDigit(c)) {
165168
throw new IllegalArgumentException("Sql cannot be parsed: illegal character in parameter or dollar-quote tag: " + c);
166169
}
167170
}
168-
return new Token(TokenType.PARAMETER, sql.substring(beginIndex));
171+
return new TokenizedSql.Token(TokenizedSql.TokenType.PARAMETER, sql.substring(beginIndex));
169172
}
170173

171-
private static Token getParameterOrDollarQuoteToken(String sql, int beginIndex) {
174+
private static TokenizedSql.Token getParameterOrDollarQuoteToken(String sql, int beginIndex) {
172175
char firstChar = sql.charAt(beginIndex + 1);
173176
if (firstChar == '$') {
174177
return getDollarQuoteToken(sql, "$$", beginIndex);
175178
} else if (isAsciiDigit(firstChar)) {
176179
return getParameterToken(sql, beginIndex);
177180
} else {
178-
for (int i = beginIndex + 1; i < sql.length(); i++) {
179-
char c = sql.charAt(i);
180-
if (c == '$') {
181-
return getDollarQuoteToken(sql, sql.substring(beginIndex, i + 1), beginIndex);
182-
}
183-
if (!(isAsciiLetter(c) || c == '_' || isAsciiDigit(c))) {
184-
throw new IllegalArgumentException("Sql cannot be parsed: illegal character in dollar-quote tag (quote opened at index " + beginIndex + ") in statement: " + sql);
185-
}
181+
for (int i = beginIndex + 1; i < sql.length(); i++) {
182+
char c = sql.charAt(i);
183+
if (c == '$') {
184+
return getDollarQuoteToken(sql, sql.substring(beginIndex, i + 1), beginIndex);
185+
}
186+
if (!(isAsciiLetter(c) || c == '_' || isAsciiDigit(c))) {
187+
throw new IllegalArgumentException("Sql cannot be parsed: illegal character in dollar-quote tag (quote opened at index " + beginIndex + ") in statement: " + sql);
186188
}
187-
throw new IllegalArgumentException("Sql cannot be parsed: unclosed dollar-quote tag(quote opened at index " + beginIndex + ") in statement: " + sql);
189+
}
190+
throw new IllegalArgumentException("Sql cannot be parsed: unclosed dollar-quote tag(quote opened at index " + beginIndex + ") in statement: " + sql);
188191
}
189192
}
190193

191-
private static Token getStandardQuoteToken(String sql, int beginIndex) {
194+
private static TokenizedSql.Token getStandardQuoteToken(String sql, int beginIndex) {
192195
int nextQuote = sql.indexOf('\'', beginIndex + 1);
193196
if (nextQuote == -1) {
194197
throw new IllegalArgumentException("Sql cannot be parsed: unclosed quote (quote opened at index " + beginIndex + ") in statement: " + sql);
195198
} else {
196-
return new Token(TokenType.STRING_CONSTANT, sql.substring(beginIndex, nextQuote + 1));
199+
return new TokenizedSql.Token(TokenizedSql.TokenType.STRING_CONSTANT, sql.substring(beginIndex, nextQuote + 1));
197200
}
198201
}
199202

200-
private static Token getQuotedIdentifierToken(String sql, int beginIndex) {
203+
private static TokenizedSql.Token getQuotedIdentifierToken(String sql, int beginIndex) {
201204
int nextQuote = sql.indexOf('\"', beginIndex + 1);
202205
if (nextQuote == -1) {
203206
throw new IllegalArgumentException("Sql cannot be parsed: unclosed quoted identifier (identifier opened at index " + beginIndex + ") in statement: " + sql);
204207
} else {
205-
return new Token(TokenType.QUOTED_IDENTIFIER, sql.substring(beginIndex, nextQuote + 1));
208+
return new TokenizedSql.Token(TokenizedSql.TokenType.QUOTED_IDENTIFIER, sql.substring(beginIndex, nextQuote + 1));
206209
}
207210
}
208211

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

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@
3030
import io.r2dbc.postgresql.util.Assert;
3131
import io.r2dbc.postgresql.util.GeneratedValuesUtils;
3232
import io.r2dbc.postgresql.util.Operators;
33-
import io.r2dbc.postgresql.util.sql.BasicPostgresqlSqlLexer;
34-
import io.r2dbc.postgresql.util.sql.TokenizedSql;
3533
import io.r2dbc.spi.Statement;
3634
import reactor.core.publisher.Flux;
3735
import reactor.core.publisher.Mono;
@@ -51,7 +49,9 @@
5149
import static io.r2dbc.postgresql.util.PredicateUtils.or;
5250

5351
/**
54-
* {@link Statement}.
52+
* A generic {@link Statement}.
53+
*
54+
* @since 0.9
5555
*/
5656
final class PostgresqlStatement implements io.r2dbc.postgresql.api.PostgresqlStatement {
5757

@@ -71,11 +71,11 @@ final class PostgresqlStatement implements io.r2dbc.postgresql.api.PostgresqlSta
7171

7272
PostgresqlStatement(ConnectionResources resources, String sql) {
7373
this.resources = Assert.requireNonNull(resources, "resources must not be null");
74-
this.tokenizedSql = BasicPostgresqlSqlLexer.tokenize(Assert.requireNonNull(sql, "sql must not be null"));
74+
this.tokenizedSql = PostgresqlSqlLexer.tokenize(Assert.requireNonNull(sql, "sql must not be null"));
7575
this.connectionContext = resources.getClient().getContext();
76-
this.bindings = new ArrayDeque<>(tokenizedSql.getParameterCount());
76+
this.bindings = new ArrayDeque<>(this.tokenizedSql.getParameterCount());
7777

78-
if (tokenizedSql.getStatementCount() > 1 && tokenizedSql.getParameterCount() > 0) {
78+
if (this.tokenizedSql.getStatementCount() > 1 && this.tokenizedSql.getParameterCount() > 0) {
7979
throw new IllegalArgumentException(String.format("Statement '%s' cannot be created. This is often due to the presence of both multiple statements and parameters at the same time.", sql));
8080
}
8181

@@ -84,11 +84,11 @@ final class PostgresqlStatement implements io.r2dbc.postgresql.api.PostgresqlSta
8484

8585
@Override
8686
public PostgresqlStatement add() {
87-
Binding binding = bindings.peekLast();
87+
Binding binding = this.bindings.peekLast();
8888
if (binding != null) {
8989
binding.validate();
9090
}
91-
this.bindings.add(new Binding(tokenizedSql.getParameterCount()));
91+
this.bindings.add(new Binding(this.tokenizedSql.getParameterCount()));
9292
return this;
9393
}
9494

@@ -115,7 +115,7 @@ public PostgresqlStatement bindNull(String identifier, Class<?> type) {
115115
public PostgresqlStatement bindNull(int index, Class<?> type) {
116116
Assert.requireNonNull(type, "type must not be null");
117117

118-
if (index >= tokenizedSql.getParameterCount()) {
118+
if (index >= this.tokenizedSql.getParameterCount()) {
119119
throw new UnsupportedOperationException(String.format("Cannot bind parameter %d, statement has %d parameters", index, this.tokenizedSql.getParameterCount()));
120120
}
121121

@@ -126,10 +126,10 @@ public PostgresqlStatement bindNull(int index, Class<?> type) {
126126

127127
@Nonnull
128128
private Binding getCurrentOrFirstBinding() {
129-
Binding binding = bindings.peekLast();
129+
Binding binding = this.bindings.peekLast();
130130
if (binding == null) {
131-
Binding newBinding = new Binding(tokenizedSql.getParameterCount());
132-
bindings.add(newBinding);
131+
Binding newBinding = new Binding(this.tokenizedSql.getParameterCount());
132+
this.bindings.add(newBinding);
133133
return newBinding;
134134
} else {
135135
return binding;
@@ -148,13 +148,11 @@ public Flux<io.r2dbc.postgresql.api.PostgresqlResult> execute() {
148148
public PostgresqlStatement returnGeneratedValues(String... columns) {
149149
Assert.requireNonNull(columns, "columns must not be null");
150150

151-
boolean hasReturning = this.tokenizedSql.hasDefaultTokenValue("RETURNING");
152-
if (hasReturning) {
151+
if (this.tokenizedSql.hasDefaultTokenValue("RETURNING")) {
153152
throw new IllegalStateException("Statement already includes RETURNING clause");
154153
}
155154

156-
boolean isSupporting = this.tokenizedSql.hasDefaultTokenValue("DELETE", "INSERT", "UPDATE");
157-
if (!isSupporting) {
155+
if (!this.tokenizedSql.hasDefaultTokenValue("DELETE", "INSERT", "UPDATE")) {
158156
throw new IllegalStateException("Statement is not a DELETE, INSERT, or UPDATE command");
159157
}
160158

0 commit comments

Comments
 (0)