Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit f9daa19

Browse files
authored
feat: add support for PostgreSQL dialect (#739)
* feat: add support for PostgreSQL dialect * fix: address review comments * deps: bump java-spanner to 6.19.1 * feat: support automatic dialect detection * deps: remove threeten + add gax * build: use owned instance for integration tests * build: use same instance as the client library
1 parent 0f53832 commit f9daa19

68 files changed

Lines changed: 5796 additions & 714 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

clirr-ignored-differences.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!-- see http://www.mojohaus.org/clirr-maven-plugin/examples/ignored-differences.html -->
3+
<differences>
4+
<difference>
5+
<differenceType>7012</differenceType>
6+
<className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
7+
<method>com.google.cloud.spanner.Dialect getDialect()</method>
8+
</difference>
9+
</differences>

pom.xml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
<site.installationModule>google-cloud-spanner-jdbc</site.installationModule>
5252
<junit.version>4.13.2</junit.version>
5353
<findbugs.version>3.0.2</findbugs.version>
54-
<threeten.version>1.4.4</threeten.version>
5554
<truth.version>1.1.3</truth.version>
5655
<mockito.version>4.3.1</mockito.version>
5756
<hamcrest.version>2.2</hamcrest.version>
@@ -63,7 +62,7 @@
6362
<dependency>
6463
<groupId>com.google.cloud</groupId>
6564
<artifactId>google-cloud-spanner-bom</artifactId>
66-
<version>6.18.0</version>
65+
<version>6.19.1</version>
6766
<type>pom</type>
6867
<scope>import</scope>
6968
</dependency>
@@ -103,6 +102,10 @@
103102
<groupId>com.google.api.grpc</groupId>
104103
<artifactId>proto-google-common-protos</artifactId>
105104
</dependency>
105+
<dependency>
106+
<groupId>com.google.api</groupId>
107+
<artifactId>gax</artifactId>
108+
</dependency>
106109
<dependency>
107110
<groupId>com.google.cloud</groupId>
108111
<artifactId>google-cloud-spanner</artifactId>
@@ -120,10 +123,6 @@
120123
<groupId>com.google.guava</groupId>
121124
<artifactId>guava</artifactId>
122125
</dependency>
123-
<dependency>
124-
<groupId>org.threeten</groupId>
125-
<artifactId>threetenbp</artifactId>
126-
</dependency>
127126
<dependency>
128127
<groupId>io.grpc</groupId>
129128
<artifactId>grpc-netty-shaded</artifactId>
@@ -234,7 +233,7 @@
234233
com.google.cloud.spanner.GceTestEnvConfig
235234
</spanner.testenv.config.class>
236235
<spanner.testenv.instance>
237-
projects/gcloud-devel/instances/spanner-testing
236+
projects/gcloud-devel/instances/spanner-testing-east1
238237
</spanner.testenv.instance>
239238
</systemPropertyVariables>
240239
<forkedProcessTimeoutInSeconds>2400</forkedProcessTimeoutInSeconds>

src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcConnection.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package com.google.cloud.spanner.jdbc;
1818

19+
import com.google.cloud.spanner.Dialect;
20+
import com.google.cloud.spanner.SpannerException;
21+
import com.google.cloud.spanner.connection.AbstractStatementParser;
1922
import com.google.cloud.spanner.connection.ConnectionOptions;
2023
import com.google.common.annotations.VisibleForTesting;
2124
import com.google.rpc.Code;
@@ -51,6 +54,7 @@ abstract class AbstractJdbcConnection extends AbstractJdbcWrapper
5154
private final ConnectionOptions options;
5255
private final com.google.cloud.spanner.connection.Connection spanner;
5356
private final Properties clientInfo;
57+
private AbstractStatementParser parser;
5458

5559
private SQLWarning firstWarning = null;
5660
private SQLWarning lastWarning = null;
@@ -76,6 +80,22 @@ ConnectionOptions getConnectionOptions() {
7680
return options;
7781
}
7882

83+
@Override
84+
public Dialect getDialect() {
85+
return spanner.getDialect();
86+
}
87+
88+
protected AbstractStatementParser getParser() throws SQLException {
89+
if (parser == null) {
90+
try {
91+
parser = AbstractStatementParser.getInstance(spanner.getDialect());
92+
} catch (SpannerException e) {
93+
throw JdbcSqlExceptionFactory.of(e);
94+
}
95+
}
96+
return parser;
97+
}
98+
7999
@Override
80100
public CallableStatement prepareCall(String sql) throws SQLException {
81101
return checkClosedAndThrowUnsupported(CALLABLE_STATEMENTS_UNSUPPORTED);

src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcPreparedStatement.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@
4242
abstract class AbstractJdbcPreparedStatement extends JdbcStatement implements PreparedStatement {
4343
private static final String METHOD_NOT_ON_PREPARED_STATEMENT =
4444
"This method may not be called on a PreparedStatement";
45-
private final JdbcParameterStore parameters = new JdbcParameterStore();
45+
private final JdbcParameterStore parameters;
4646

47-
AbstractJdbcPreparedStatement(JdbcConnection connection) {
47+
AbstractJdbcPreparedStatement(JdbcConnection connection) throws SQLException {
4848
super(connection);
49+
parameters = new JdbcParameterStore(connection.getDialect());
4950
}
5051

5152
JdbcParameterStore getParameters() {

src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.cloud.spanner.Options.QueryOption;
2121
import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
2222
import com.google.cloud.spanner.SpannerException;
23+
import com.google.cloud.spanner.connection.AbstractStatementParser;
2324
import com.google.cloud.spanner.connection.Connection;
2425
import com.google.cloud.spanner.connection.StatementResult;
2526
import com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType;
@@ -35,14 +36,16 @@
3536
abstract class AbstractJdbcStatement extends AbstractJdbcWrapper implements Statement {
3637
private static final String CURSORS_NOT_SUPPORTED = "Cursors are not supported";
3738
private static final String ONLY_FETCH_FORWARD_SUPPORTED = "Only fetch_forward is supported";
39+
final AbstractStatementParser parser;
3840
private boolean closed;
3941
private boolean closeOnCompletion;
4042
private boolean poolable;
4143
private final JdbcConnection connection;
4244
private int queryTimeout;
4345

44-
AbstractJdbcStatement(JdbcConnection connection) {
46+
AbstractJdbcStatement(JdbcConnection connection) throws SQLException {
4547
this.connection = connection;
48+
this.parser = connection.getParser();
4649
}
4750

4851
@Override

src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcWrapper.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ static int extractColumnType(Type type) {
4747
if (type.equals(Type.float64())) return Types.DOUBLE;
4848
if (type.equals(Type.int64())) return Types.BIGINT;
4949
if (type.equals(Type.numeric())) return Types.NUMERIC;
50+
if (type.equals(Type.pgNumeric())) return Types.NUMERIC;
5051
if (type.equals(Type.string())) return Types.NVARCHAR;
5152
if (type.equals(Type.json())) return Types.NVARCHAR;
5253
if (type.equals(Type.timestamp())) return Types.TIMESTAMP;
@@ -106,6 +107,7 @@ static String getClassName(Type type) {
106107
if (type == Type.float64()) return Double.class.getName();
107108
if (type == Type.int64()) return Long.class.getName();
108109
if (type == Type.numeric()) return BigDecimal.class.getName();
110+
if (type == Type.pgNumeric()) return BigDecimal.class.getName();
109111
if (type == Type.string()) return String.class.getName();
110112
if (type == Type.json()) return String.class.getName();
111113
if (type == Type.timestamp()) return Timestamp.class.getName();
@@ -116,6 +118,7 @@ static String getClassName(Type type) {
116118
if (type.getArrayElementType() == Type.float64()) return Double[].class.getName();
117119
if (type.getArrayElementType() == Type.int64()) return Long[].class.getName();
118120
if (type.getArrayElementType() == Type.numeric()) return BigDecimal[].class.getName();
121+
if (type.getArrayElementType() == Type.pgNumeric()) return BigDecimal[].class.getName();
119122
if (type.getArrayElementType() == Type.string()) return String[].class.getName();
120123
if (type.getArrayElementType() == Type.json()) return String[].class.getName();
121124
if (type.getArrayElementType() == Type.timestamp()) return Timestamp[].class.getName();
@@ -145,6 +148,16 @@ static byte checkedCastToByte(BigDecimal val) throws SQLException {
145148
}
146149
}
147150

151+
/** Cast value and throw {@link SQLException} if out-of-range. */
152+
static byte checkedCastToByte(BigInteger val) throws SQLException {
153+
try {
154+
return val.byteValueExact();
155+
} catch (ArithmeticException e) {
156+
throw JdbcSqlExceptionFactory.of(
157+
String.format(OUT_OF_RANGE_MSG, "byte", val), com.google.rpc.Code.OUT_OF_RANGE);
158+
}
159+
}
160+
148161
/** Cast value and throw {@link SQLException} if out-of-range. */
149162
static short checkedCastToShort(long val) throws SQLException {
150163
if (val > Short.MAX_VALUE || val < Short.MIN_VALUE) {
@@ -164,6 +177,16 @@ static short checkedCastToShort(BigDecimal val) throws SQLException {
164177
}
165178
}
166179

180+
/** Cast value and throw {@link SQLException} if out-of-range. */
181+
static short checkedCastToShort(BigInteger val) throws SQLException {
182+
try {
183+
return val.shortValueExact();
184+
} catch (ArithmeticException e) {
185+
throw JdbcSqlExceptionFactory.of(
186+
String.format(OUT_OF_RANGE_MSG, "short", val), com.google.rpc.Code.OUT_OF_RANGE);
187+
}
188+
}
189+
167190
/** Cast value and throw {@link SQLException} if out-of-range. */
168191
static int checkedCastToInt(long val) throws SQLException {
169192
if (val > Integer.MAX_VALUE || val < Integer.MIN_VALUE) {
@@ -183,6 +206,16 @@ static int checkedCastToInt(BigDecimal val) throws SQLException {
183206
}
184207
}
185208

209+
/** Cast value and throw {@link SQLException} if out-of-range. */
210+
static int checkedCastToInt(BigInteger val) throws SQLException {
211+
try {
212+
return val.intValueExact();
213+
} catch (ArithmeticException e) {
214+
throw JdbcSqlExceptionFactory.of(
215+
String.format(OUT_OF_RANGE_MSG, "int", val), com.google.rpc.Code.OUT_OF_RANGE);
216+
}
217+
}
218+
186219
/** Cast value and throw {@link SQLException} if out-of-range. */
187220
static float checkedCastToFloat(double val) throws SQLException {
188221
if (val > Float.MAX_VALUE || val < -Float.MAX_VALUE) {
@@ -226,6 +259,16 @@ static long checkedCastToLong(BigDecimal val) throws SQLException {
226259
}
227260
}
228261

262+
/** Cast value and throw {@link SQLException} if out-of-range. */
263+
static long checkedCastToLong(BigInteger val) throws SQLException {
264+
try {
265+
return val.longValueExact();
266+
} catch (ArithmeticException e) {
267+
throw JdbcSqlExceptionFactory.of(
268+
String.format(OUT_OF_RANGE_MSG, "long", val), com.google.rpc.Code.OUT_OF_RANGE);
269+
}
270+
}
271+
229272
/**
230273
* Parses the given string value as a double. Throws {@link SQLException} if the string is not a
231274
* valid double value.
@@ -240,6 +283,20 @@ static double parseDouble(String val) throws SQLException {
240283
}
241284
}
242285

286+
/**
287+
* Parses the given string value as a float. Throws {@link SQLException} if the string is not a
288+
* valid float value.
289+
*/
290+
static float parseFloat(String val) throws SQLException {
291+
Preconditions.checkNotNull(val);
292+
try {
293+
return Float.parseFloat(val);
294+
} catch (NumberFormatException e) {
295+
throw JdbcSqlExceptionFactory.of(
296+
String.format("%s is not a valid number", val), com.google.rpc.Code.INVALID_ARGUMENT, e);
297+
}
298+
}
299+
243300
/**
244301
* Parses the given string value as a {@link Date} value. Throws {@link SQLException} if the
245302
* string is not a valid {@link Date} value.
@@ -332,6 +389,20 @@ static Timestamp parseTimestamp(String val, Calendar cal) throws SQLException {
332389
}
333390
}
334391

392+
/**
393+
* Parses the given string value as a {@link BigDecimal} value. Throws {@link SQLException} if the
394+
* string is not a valid {@link BigDecimal} value.
395+
*/
396+
static BigDecimal parseBigDecimal(String val) throws SQLException {
397+
Preconditions.checkNotNull(val);
398+
try {
399+
return new BigDecimal(val);
400+
} catch (NumberFormatException e) {
401+
throw JdbcSqlExceptionFactory.of(
402+
String.format("%s is not a valid number", val), com.google.rpc.Code.INVALID_ARGUMENT, e);
403+
}
404+
}
405+
335406
/** Should return true if this object has been closed */
336407
public abstract boolean isClosed();
337408

src/main/java/com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.cloud.spanner.AbortedException;
2121
import com.google.cloud.spanner.CommitResponse;
2222
import com.google.cloud.spanner.CommitStats;
23+
import com.google.cloud.spanner.Dialect;
2324
import com.google.cloud.spanner.Mutation;
2425
import com.google.cloud.spanner.ResultSet;
2526
import com.google.cloud.spanner.TimestampBound;
@@ -321,6 +322,11 @@ default String getStatementTag() throws SQLException {
321322
*/
322323
String getConnectionUrl();
323324

325+
/** @return The {@link Dialect} that is used by this connection. */
326+
default Dialect getDialect() {
327+
return Dialect.GOOGLE_STANDARD_SQL;
328+
}
329+
324330
/**
325331
* @see
326332
* com.google.cloud.spanner.connection.Connection#addTransactionRetryListener(com.google.cloud.spanner.connection.TransactionRetryListener)

src/main/java/com/google/cloud/spanner/jdbc/JdbcConnection.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import com.google.cloud.spanner.TimestampBound;
2424
import com.google.cloud.spanner.connection.AutocommitDmlMode;
2525
import com.google.cloud.spanner.connection.ConnectionOptions;
26-
import com.google.cloud.spanner.connection.StatementParser;
2726
import com.google.cloud.spanner.connection.TransactionMode;
2827
import com.google.common.collect.Iterators;
2928
import java.sql.Array;
@@ -72,8 +71,8 @@ public JdbcPreparedStatement prepareStatement(String sql) throws SQLException {
7271
@Override
7372
public String nativeSQL(String sql) throws SQLException {
7473
checkClosed();
75-
return JdbcParameterStore.convertPositionalParametersToNamedParameters(
76-
StatementParser.removeCommentsAndTrim(sql))
74+
return getParser()
75+
.convertPositionalParametersToNamedParameters('?', getParser().removeCommentsAndTrim(sql))
7776
.sqlWithNamedParameters;
7877
}
7978

src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,32 @@ public Type getSpannerType() {
203203
return Type.numeric();
204204
}
205205
},
206+
PG_NUMERIC {
207+
@Override
208+
public int getSqlType() {
209+
return Types.NUMERIC;
210+
}
211+
212+
@Override
213+
public Class<BigDecimal> getJavaClass() {
214+
return BigDecimal.class;
215+
}
216+
217+
@Override
218+
public Code getCode() {
219+
return Code.PG_NUMERIC;
220+
}
221+
222+
@Override
223+
public List<BigDecimal> getArrayElements(ResultSet rs, int columnIndex) {
224+
return rs.getValue(columnIndex).getNumericArray();
225+
}
226+
227+
@Override
228+
public Type getSpannerType() {
229+
return Type.pgNumeric();
230+
}
231+
},
206232
STRING {
207233
@Override
208234
public int getSqlType() {

src/main/java/com/google/cloud/spanner/jdbc/JdbcParameterMetaData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package com.google.cloud.spanner.jdbc;
1818

19-
import com.google.cloud.spanner.jdbc.JdbcParameterStore.ParametersInfo;
19+
import com.google.cloud.spanner.connection.AbstractStatementParser.ParametersInfo;
2020
import java.math.BigDecimal;
2121
import java.sql.Date;
2222
import java.sql.ParameterMetaData;

0 commit comments

Comments
 (0)