Skip to content

Commit

Permalink
feat: add support for PostgreSQL dialect (#739)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
olavloite committed Feb 19, 2022
1 parent 0f53832 commit f9daa19
Show file tree
Hide file tree
Showing 68 changed files with 5,796 additions and 714 deletions.
9 changes: 9 additions & 0 deletions clirr-ignored-differences.xml
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- see http://www.mojohaus.org/clirr-maven-plugin/examples/ignored-differences.html -->
<differences>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection</className>
<method>com.google.cloud.spanner.Dialect getDialect()</method>
</difference>
</differences>
13 changes: 6 additions & 7 deletions pom.xml
Expand Up @@ -51,7 +51,6 @@
<site.installationModule>google-cloud-spanner-jdbc</site.installationModule>
<junit.version>4.13.2</junit.version>
<findbugs.version>3.0.2</findbugs.version>
<threeten.version>1.4.4</threeten.version>
<truth.version>1.1.3</truth.version>
<mockito.version>4.3.1</mockito.version>
<hamcrest.version>2.2</hamcrest.version>
Expand All @@ -63,7 +62,7 @@
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-spanner-bom</artifactId>
<version>6.18.0</version>
<version>6.19.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand Down Expand Up @@ -103,6 +102,10 @@
<groupId>com.google.api.grpc</groupId>
<artifactId>proto-google-common-protos</artifactId>
</dependency>
<dependency>
<groupId>com.google.api</groupId>
<artifactId>gax</artifactId>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-spanner</artifactId>
Expand All @@ -120,10 +123,6 @@
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.threeten</groupId>
<artifactId>threetenbp</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
Expand Down Expand Up @@ -234,7 +233,7 @@
com.google.cloud.spanner.GceTestEnvConfig
</spanner.testenv.config.class>
<spanner.testenv.instance>
projects/gcloud-devel/instances/spanner-testing
projects/gcloud-devel/instances/spanner-testing-east1
</spanner.testenv.instance>
</systemPropertyVariables>
<forkedProcessTimeoutInSeconds>2400</forkedProcessTimeoutInSeconds>
Expand Down
Expand Up @@ -16,6 +16,9 @@

package com.google.cloud.spanner.jdbc;

import com.google.cloud.spanner.Dialect;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.connection.AbstractStatementParser;
import com.google.cloud.spanner.connection.ConnectionOptions;
import com.google.common.annotations.VisibleForTesting;
import com.google.rpc.Code;
Expand Down Expand Up @@ -51,6 +54,7 @@ abstract class AbstractJdbcConnection extends AbstractJdbcWrapper
private final ConnectionOptions options;
private final com.google.cloud.spanner.connection.Connection spanner;
private final Properties clientInfo;
private AbstractStatementParser parser;

private SQLWarning firstWarning = null;
private SQLWarning lastWarning = null;
Expand All @@ -76,6 +80,22 @@ ConnectionOptions getConnectionOptions() {
return options;
}

@Override
public Dialect getDialect() {
return spanner.getDialect();
}

protected AbstractStatementParser getParser() throws SQLException {
if (parser == null) {
try {
parser = AbstractStatementParser.getInstance(spanner.getDialect());
} catch (SpannerException e) {
throw JdbcSqlExceptionFactory.of(e);
}
}
return parser;
}

@Override
public CallableStatement prepareCall(String sql) throws SQLException {
return checkClosedAndThrowUnsupported(CALLABLE_STATEMENTS_UNSUPPORTED);
Expand Down
Expand Up @@ -42,10 +42,11 @@
abstract class AbstractJdbcPreparedStatement extends JdbcStatement implements PreparedStatement {
private static final String METHOD_NOT_ON_PREPARED_STATEMENT =
"This method may not be called on a PreparedStatement";
private final JdbcParameterStore parameters = new JdbcParameterStore();
private final JdbcParameterStore parameters;

AbstractJdbcPreparedStatement(JdbcConnection connection) {
AbstractJdbcPreparedStatement(JdbcConnection connection) throws SQLException {
super(connection);
parameters = new JdbcParameterStore(connection.getDialect());
}

JdbcParameterStore getParameters() {
Expand Down
Expand Up @@ -20,6 +20,7 @@
import com.google.cloud.spanner.Options.QueryOption;
import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.connection.AbstractStatementParser;
import com.google.cloud.spanner.connection.Connection;
import com.google.cloud.spanner.connection.StatementResult;
import com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType;
Expand All @@ -35,14 +36,16 @@
abstract class AbstractJdbcStatement extends AbstractJdbcWrapper implements Statement {
private static final String CURSORS_NOT_SUPPORTED = "Cursors are not supported";
private static final String ONLY_FETCH_FORWARD_SUPPORTED = "Only fetch_forward is supported";
final AbstractStatementParser parser;
private boolean closed;
private boolean closeOnCompletion;
private boolean poolable;
private final JdbcConnection connection;
private int queryTimeout;

AbstractJdbcStatement(JdbcConnection connection) {
AbstractJdbcStatement(JdbcConnection connection) throws SQLException {
this.connection = connection;
this.parser = connection.getParser();
}

@Override
Expand Down
Expand Up @@ -47,6 +47,7 @@ static int extractColumnType(Type type) {
if (type.equals(Type.float64())) return Types.DOUBLE;
if (type.equals(Type.int64())) return Types.BIGINT;
if (type.equals(Type.numeric())) return Types.NUMERIC;
if (type.equals(Type.pgNumeric())) return Types.NUMERIC;
if (type.equals(Type.string())) return Types.NVARCHAR;
if (type.equals(Type.json())) return Types.NVARCHAR;
if (type.equals(Type.timestamp())) return Types.TIMESTAMP;
Expand Down Expand Up @@ -106,6 +107,7 @@ static String getClassName(Type type) {
if (type == Type.float64()) return Double.class.getName();
if (type == Type.int64()) return Long.class.getName();
if (type == Type.numeric()) return BigDecimal.class.getName();
if (type == Type.pgNumeric()) return BigDecimal.class.getName();
if (type == Type.string()) return String.class.getName();
if (type == Type.json()) return String.class.getName();
if (type == Type.timestamp()) return Timestamp.class.getName();
Expand All @@ -116,6 +118,7 @@ static String getClassName(Type type) {
if (type.getArrayElementType() == Type.float64()) return Double[].class.getName();
if (type.getArrayElementType() == Type.int64()) return Long[].class.getName();
if (type.getArrayElementType() == Type.numeric()) return BigDecimal[].class.getName();
if (type.getArrayElementType() == Type.pgNumeric()) return BigDecimal[].class.getName();
if (type.getArrayElementType() == Type.string()) return String[].class.getName();
if (type.getArrayElementType() == Type.json()) return String[].class.getName();
if (type.getArrayElementType() == Type.timestamp()) return Timestamp[].class.getName();
Expand Down Expand Up @@ -145,6 +148,16 @@ static byte checkedCastToByte(BigDecimal val) throws SQLException {
}
}

/** Cast value and throw {@link SQLException} if out-of-range. */
static byte checkedCastToByte(BigInteger val) throws SQLException {
try {
return val.byteValueExact();
} catch (ArithmeticException e) {
throw JdbcSqlExceptionFactory.of(
String.format(OUT_OF_RANGE_MSG, "byte", val), com.google.rpc.Code.OUT_OF_RANGE);
}
}

/** Cast value and throw {@link SQLException} if out-of-range. */
static short checkedCastToShort(long val) throws SQLException {
if (val > Short.MAX_VALUE || val < Short.MIN_VALUE) {
Expand All @@ -164,6 +177,16 @@ static short checkedCastToShort(BigDecimal val) throws SQLException {
}
}

/** Cast value and throw {@link SQLException} if out-of-range. */
static short checkedCastToShort(BigInteger val) throws SQLException {
try {
return val.shortValueExact();
} catch (ArithmeticException e) {
throw JdbcSqlExceptionFactory.of(
String.format(OUT_OF_RANGE_MSG, "short", val), com.google.rpc.Code.OUT_OF_RANGE);
}
}

/** Cast value and throw {@link SQLException} if out-of-range. */
static int checkedCastToInt(long val) throws SQLException {
if (val > Integer.MAX_VALUE || val < Integer.MIN_VALUE) {
Expand All @@ -183,6 +206,16 @@ static int checkedCastToInt(BigDecimal val) throws SQLException {
}
}

/** Cast value and throw {@link SQLException} if out-of-range. */
static int checkedCastToInt(BigInteger val) throws SQLException {
try {
return val.intValueExact();
} catch (ArithmeticException e) {
throw JdbcSqlExceptionFactory.of(
String.format(OUT_OF_RANGE_MSG, "int", val), com.google.rpc.Code.OUT_OF_RANGE);
}
}

/** Cast value and throw {@link SQLException} if out-of-range. */
static float checkedCastToFloat(double val) throws SQLException {
if (val > Float.MAX_VALUE || val < -Float.MAX_VALUE) {
Expand Down Expand Up @@ -226,6 +259,16 @@ static long checkedCastToLong(BigDecimal val) throws SQLException {
}
}

/** Cast value and throw {@link SQLException} if out-of-range. */
static long checkedCastToLong(BigInteger val) throws SQLException {
try {
return val.longValueExact();
} catch (ArithmeticException e) {
throw JdbcSqlExceptionFactory.of(
String.format(OUT_OF_RANGE_MSG, "long", val), com.google.rpc.Code.OUT_OF_RANGE);
}
}

/**
* Parses the given string value as a double. Throws {@link SQLException} if the string is not a
* valid double value.
Expand All @@ -240,6 +283,20 @@ static double parseDouble(String val) throws SQLException {
}
}

/**
* Parses the given string value as a float. Throws {@link SQLException} if the string is not a
* valid float value.
*/
static float parseFloat(String val) throws SQLException {
Preconditions.checkNotNull(val);
try {
return Float.parseFloat(val);
} catch (NumberFormatException e) {
throw JdbcSqlExceptionFactory.of(
String.format("%s is not a valid number", val), com.google.rpc.Code.INVALID_ARGUMENT, e);
}
}

/**
* Parses the given string value as a {@link Date} value. Throws {@link SQLException} if the
* string is not a valid {@link Date} value.
Expand Down Expand Up @@ -332,6 +389,20 @@ static Timestamp parseTimestamp(String val, Calendar cal) throws SQLException {
}
}

/**
* Parses the given string value as a {@link BigDecimal} value. Throws {@link SQLException} if the
* string is not a valid {@link BigDecimal} value.
*/
static BigDecimal parseBigDecimal(String val) throws SQLException {
Preconditions.checkNotNull(val);
try {
return new BigDecimal(val);
} catch (NumberFormatException e) {
throw JdbcSqlExceptionFactory.of(
String.format("%s is not a valid number", val), com.google.rpc.Code.INVALID_ARGUMENT, e);
}
}

/** Should return true if this object has been closed */
public abstract boolean isClosed();

Expand Down
Expand Up @@ -20,6 +20,7 @@
import com.google.cloud.spanner.AbortedException;
import com.google.cloud.spanner.CommitResponse;
import com.google.cloud.spanner.CommitStats;
import com.google.cloud.spanner.Dialect;
import com.google.cloud.spanner.Mutation;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.TimestampBound;
Expand Down Expand Up @@ -321,6 +322,11 @@ default String getStatementTag() throws SQLException {
*/
String getConnectionUrl();

/** @return The {@link Dialect} that is used by this connection. */
default Dialect getDialect() {
return Dialect.GOOGLE_STANDARD_SQL;
}

/**
* @see
* com.google.cloud.spanner.connection.Connection#addTransactionRetryListener(com.google.cloud.spanner.connection.TransactionRetryListener)
Expand Down
Expand Up @@ -23,7 +23,6 @@
import com.google.cloud.spanner.TimestampBound;
import com.google.cloud.spanner.connection.AutocommitDmlMode;
import com.google.cloud.spanner.connection.ConnectionOptions;
import com.google.cloud.spanner.connection.StatementParser;
import com.google.cloud.spanner.connection.TransactionMode;
import com.google.common.collect.Iterators;
import java.sql.Array;
Expand Down Expand Up @@ -72,8 +71,8 @@ public JdbcPreparedStatement prepareStatement(String sql) throws SQLException {
@Override
public String nativeSQL(String sql) throws SQLException {
checkClosed();
return JdbcParameterStore.convertPositionalParametersToNamedParameters(
StatementParser.removeCommentsAndTrim(sql))
return getParser()
.convertPositionalParametersToNamedParameters('?', getParser().removeCommentsAndTrim(sql))
.sqlWithNamedParameters;
}

Expand Down
26 changes: 26 additions & 0 deletions src/main/java/com/google/cloud/spanner/jdbc/JdbcDataType.java
Expand Up @@ -203,6 +203,32 @@ public Type getSpannerType() {
return Type.numeric();
}
},
PG_NUMERIC {
@Override
public int getSqlType() {
return Types.NUMERIC;
}

@Override
public Class<BigDecimal> getJavaClass() {
return BigDecimal.class;
}

@Override
public Code getCode() {
return Code.PG_NUMERIC;
}

@Override
public List<BigDecimal> getArrayElements(ResultSet rs, int columnIndex) {
return rs.getValue(columnIndex).getNumericArray();
}

@Override
public Type getSpannerType() {
return Type.pgNumeric();
}
},
STRING {
@Override
public int getSqlType() {
Expand Down
Expand Up @@ -16,7 +16,7 @@

package com.google.cloud.spanner.jdbc;

import com.google.cloud.spanner.jdbc.JdbcParameterStore.ParametersInfo;
import com.google.cloud.spanner.connection.AbstractStatementParser.ParametersInfo;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.ParameterMetaData;
Expand Down

0 comments on commit f9daa19

Please sign in to comment.