parameters) {
- switch (this.parsedStatement.getType()) {
- case QUERY:
- return transformSelectToSelectParams(
- this.parsedStatement.getSqlWithoutComments(), parameters);
- case UPDATE:
- return transformDmlToSelectParams(parameters);
- case CLIENT_SIDE:
- case DDL:
- case UNKNOWN:
- default:
- return Statement.of(this.parsedStatement.getSqlWithoutComments());
- }
- }
-
- /**
- * Transforms a query into one that selects the parameters in the query.
- *
- * Example: select id, value from foo where value like $1
is transformed to
- * select $1, $2 from (select id, value from foo where value like $1) p
- */
- private static Statement transformSelectToSelectParams(String sql, Set parameters) {
- return Statement.of(String.format("select %s from (%s) p", String.join(", ", parameters), sql));
- }
-
- /**
- * Transforms a DML statement into a SELECT statement that selects the parameters in the DML
- * statement.
- */
- private Statement transformDmlToSelectParams(Set parameters) {
- switch (getCommand()) {
- case "INSERT":
- return transformInsertToSelectParams(
- this.connection, this.parsedStatement.getSqlWithoutComments(), parameters);
- case "UPDATE":
- return transformUpdateToSelectParams(
- this.parsedStatement.getSqlWithoutComments(), parameters);
- case "DELETE":
- return transformDeleteToSelectParams(
- this.parsedStatement.getSqlWithoutComments(), parameters);
- default:
- return null;
- }
- }
-
- /**
- * Transforms an INSERT statement into a SELECT statement that selects the parameters in the
- * insert statement. The way this is done depends on whether the INSERT statement uses a VALUES
- * clause or a SELECT statement. If the INSERT statement uses a SELECT clause, the same strategy
- * is used as for normal SELECT statements. For INSERT statements with a VALUES clause, a SELECT
- * statement is created that selects a comparison between the column where a value is inserted and
- * the expression that is used to insert a value in the column.
- *
- * Examples:
- *
- *
- * insert into foo (id, value) values ($1, $2)
is transformed to
- * select $1, $2 from (select id=$1, value=$2 from foo) p
- *
- * insert into bar (id, value, created_at) values (1, $1 + sqrt($2), current_timestamp())
- *
is transformed to
- * select $1, $2 from (select value=$1 + sqrt($2) from bar) p
- * insert into foo values ($1, $2)
is transformed to
- * select $1, $2 from (select id=$1, value=$2 from foo) p
- *
- */
- @VisibleForTesting
- static @Nullable Statement transformInsertToSelectParams(
- Connection connection, String sql, Set parameters) {
- SimpleParser parser = new SimpleParser(sql);
- if (!parser.eatKeyword("insert")) {
- return null;
- }
- parser.eatKeyword("into");
- TableOrIndexName table = parser.readTableOrIndexName();
- if (table == null) {
- return null;
- }
- parser.skipWhitespaces();
-
- List columnsList = null;
- int posBeforeToken = parser.getPos();
- if (parser.eatToken("(")) {
- if (parser.peekKeyword("select") || parser.peekToken("(")) {
- // Revert and assume that the insert uses a select statement.
- parser.setPos(posBeforeToken);
- } else {
- columnsList = parser.parseExpressionList();
- if (!parser.eatToken(")")) {
- return null;
- }
- }
- }
-
- parser.skipWhitespaces();
- int potentialSelectStart = parser.getPos();
- if (!parser.eatKeyword("values")) {
- while (parser.eatToken("(")) {
- // ignore
- }
- if (parser.eatKeyword("select")) {
- // This is an `insert into [(...)] select ...` statement. Then we can just use the
- // select statement as the result.
- return transformSelectToSelectParams(
- parser.getSql().substring(potentialSelectStart), parameters);
- }
- return null;
- }
-
- if (columnsList == null || columnsList.isEmpty()) {
- columnsList = getAllColumns(connection, table);
- }
- List> rows = new ArrayList<>();
- while (parser.eatToken("(")) {
- List row = parser.parseExpressionList();
- if (row == null
- || row.isEmpty()
- || !parser.eatToken(")")
- || row.size() != columnsList.size()) {
- return null;
- }
- rows.add(row);
- if (!parser.eatToken(",")) {
- break;
- }
- }
- if (rows.isEmpty()) {
- return null;
- }
- StringBuilder select = new StringBuilder("select ");
- select.append(String.join(", ", parameters)).append(" from (select ");
-
- int columnIndex = 0;
- int colCount = rows.size() * columnsList.size();
- for (List row : rows) {
- for (int index = 0; index < row.size(); index++) {
- select.append(columnsList.get(index)).append("=").append(row.get(index));
- columnIndex++;
- if (columnIndex < colCount) {
- select.append(", ");
- }
- }
- }
- select.append(" from ").append(table).append(") p");
-
- return Statement.of(select.toString());
- }
-
- /**
- * Returns a list of all columns in the given table. This is used to transform insert statements
- * without a column list. The query that is used does not use the INFORMATION_SCHEMA, but queries
- * the table directly, so it can use the same transaction as the actual insert statement.
- */
- static List getAllColumns(Connection connection, TableOrIndexName table) {
- try (ResultSet resultSet =
- connection.analyzeQuery(
- Statement.of("SELECT * FROM " + table + " LIMIT 1"), QueryAnalyzeMode.PLAN)) {
- return resultSet.getType().getStructFields().stream()
- .map(StructField::getName)
- .collect(Collectors.toList());
- }
- }
-
- /**
- * Transforms an UPDATE statement into a SELECT statement that selects the parameters in the
- * update statement. This is done by creating a SELECT statement that selects the assignment
- * expressions in the UPDATE statement, followed by the WHERE clause of the UPDATE statement.
- *
- * Examples:
- *
- *
- * update foo set value=$1 where id=$2
is transformed to
- * select $1, $2 from (select value=$1 from foo where id=$2) p
- * update bar set value=$1+sqrt($2), updated_at=current_timestamp()
is
- * transformed to select $1, $2 from (select value=$1+sqrt($2) from foo) p
- *
- */
- @VisibleForTesting
- static Statement transformUpdateToSelectParams(String sql, Set parameters) {
- SimpleParser parser = new SimpleParser(sql);
- if (!parser.eatKeyword("update")) {
- return null;
- }
- parser.eatKeyword("only");
- TableOrIndexName table = parser.readTableOrIndexName();
- if (table == null) {
- return null;
- }
- if (!parser.eatKeyword("set")) {
- return null;
- }
- List assignmentsList = parser.parseExpressionListUntilKeyword("where", true);
- if (assignmentsList == null || assignmentsList.isEmpty()) {
- return null;
- }
- int whereStart = parser.getPos();
- if (!parser.eatKeyword("where")) {
- whereStart = -1;
- }
-
- StringBuilder select = new StringBuilder("select ");
- select
- .append(String.join(", ", parameters))
- .append(" from (select ")
- .append(String.join(", ", assignmentsList))
- .append(" from ")
- .append(table);
- if (whereStart > -1) {
- select.append(" ").append(sql.substring(whereStart));
- }
- select.append(") p");
-
- return Statement.of(select.toString());
- }
-
- /**
- * Transforms a DELETE statement into a SELECT statement that selects the parameters of the DELETE
- * statement. This is done by creating a SELECT 1 FROM table_name WHERE ... statement from the
- * DELETE statement.
- *
- * Example:
- *
- *
- * DELETE FROM foo WHERE id=$1
is transformed to
- * SELECT $1 FROM (SELECT 1 FROM foo WHERE id=$1) p
- *
- */
- @VisibleForTesting
- static Statement transformDeleteToSelectParams(String sql, Set parameters) {
- SimpleParser parser = new SimpleParser(sql);
- if (!parser.eatKeyword("delete")) {
- return null;
- }
- parser.eatKeyword("from");
- TableOrIndexName table = parser.readTableOrIndexName();
- if (table == null) {
- return null;
- }
- parser.skipWhitespaces();
- int whereStart = parser.getPos();
- if (!parser.eatKeyword("where")) {
- // Deletes must have a where clause, otherwise there cannot be any parameters.
- return null;
- }
-
- StringBuilder select =
- new StringBuilder("select ")
- .append(String.join(", ", parameters))
- .append(" from (select 1 from ")
- .append(table)
- .append(" ")
- .append(sql.substring(whereStart))
- .append(") p");
-
- return Statement.of(select.toString());
- }
-
- /**
- * Returns the parameter types in the SQL string of this statement. The current implementation
- * always returns any parameters that may have been specified in the PARSE message, and
- * OID.Unspecified for all other parameters.
- */
- private void extractParameterTypes(ResultSet paramsResultSet) {
- paramsResultSet.next();
- ensureParameterLength(paramsResultSet.getColumnCount());
- for (int i = 0; i < paramsResultSet.getColumnCount(); i++) {
- // Only override parameter types that were not specified by the frontend.
- if (this.parameterDataTypes[i] == 0) {
- this.parameterDataTypes[i] = Parser.toOid(paramsResultSet.getColumnType(i));
- }
- }
- }
-
- /**
- * Enlarges the size of the parameter types of this statement to match the given count. Existing
- * parameter types are preserved. New parameters are set to OID.Unspecified.
- */
- private void ensureParameterLength(int parameterCount) {
- if (this.parameterDataTypes == null) {
- this.parameterDataTypes = new int[parameterCount];
- } else if (this.parameterDataTypes.length != parameterCount) {
- this.parameterDataTypes = Arrays.copyOf(this.parameterDataTypes, parameterCount);
- }
- }
}
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatement.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatement.java
index f7e4a82977..73bdfcf985 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatement.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatement.java
@@ -30,7 +30,7 @@
import com.google.cloud.spanner.pgadapter.ConnectionHandler;
import com.google.cloud.spanner.pgadapter.error.PGException;
import com.google.cloud.spanner.pgadapter.error.PGExceptionFactory;
-import com.google.cloud.spanner.pgadapter.metadata.DescribeMetadata;
+import com.google.cloud.spanner.pgadapter.metadata.DescribeResult;
import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
import com.google.cloud.spanner.pgadapter.statements.BackendConnection.NoResult;
import com.google.cloud.spanner.pgadapter.utils.Converter;
@@ -75,6 +75,7 @@ public enum ResultNotReadyBehavior {
protected final Statement originalStatement;
protected final String command;
protected String commandTag;
+ protected boolean described;
protected boolean executed;
protected final Connection connection;
protected final ConnectionHandler connectionHandler;
@@ -290,6 +291,10 @@ public void handleExecutionException(PGException exception) {
this.hasMoreData = false;
}
+ public boolean isDescribed() {
+ return this.described;
+ }
+
public void executeAsync(BackendConnection backendConnection) {
throw new UnsupportedOperationException();
}
@@ -298,12 +303,12 @@ public void executeAsync(BackendConnection backendConnection) {
* Moreso meant for inherited classes, allows one to call describe on a statement. Since raw
* statements cannot be described, throw an error.
*/
- public DescribeMetadata> describe() {
+ public DescribeResult describe() {
throw new IllegalStateException(
"Cannot describe a simple statement " + "(only prepared statements and portals)");
}
- public Future extends DescribeMetadata>> describeAsync(BackendConnection backendConnection) {
+ public Future describeAsync(BackendConnection backendConnection) {
throw new UnsupportedOperationException();
}
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/InvalidStatement.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/InvalidStatement.java
index 2cce42c89c..c3b12977a7 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/InvalidStatement.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/InvalidStatement.java
@@ -19,6 +19,7 @@
import com.google.cloud.spanner.pgadapter.ConnectionHandler;
import com.google.cloud.spanner.pgadapter.error.PGExceptionFactory;
import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
+import com.google.common.collect.ImmutableList;
public class InvalidStatement extends IntermediatePortalStatement {
@@ -29,7 +30,18 @@ public InvalidStatement(
ParsedStatement parsedStatement,
Statement originalStatement,
Exception exception) {
- super(connectionHandler, options, name, parsedStatement, originalStatement);
+ super(
+ name,
+ new IntermediatePreparedStatement(
+ connectionHandler,
+ options,
+ name,
+ NO_PARAMETER_TYPES,
+ parsedStatement,
+ originalStatement),
+ NO_PARAMS,
+ ImmutableList.of(),
+ ImmutableList.of());
setException(PGExceptionFactory.toPGException(exception));
}
}
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/PrepareStatement.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/PrepareStatement.java
index d8f6d20bb8..bd69f46172 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/PrepareStatement.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/PrepareStatement.java
@@ -20,9 +20,9 @@
import com.google.cloud.spanner.connection.AbstractStatementParser;
import com.google.cloud.spanner.connection.AbstractStatementParser.ParsedStatement;
import com.google.cloud.spanner.connection.AbstractStatementParser.StatementType;
+import com.google.cloud.spanner.connection.StatementResult;
import com.google.cloud.spanner.pgadapter.ConnectionHandler;
import com.google.cloud.spanner.pgadapter.error.PGExceptionFactory;
-import com.google.cloud.spanner.pgadapter.metadata.DescribePortalMetadata;
import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata;
import com.google.cloud.spanner.pgadapter.statements.BackendConnection.NoResult;
import com.google.cloud.spanner.pgadapter.statements.SimpleParser.TableOrIndexName;
@@ -108,7 +108,18 @@ public PrepareStatement(
String name,
ParsedStatement parsedStatement,
Statement originalStatement) {
- super(connectionHandler, options, name, parsedStatement, originalStatement);
+ super(
+ name,
+ new IntermediatePreparedStatement(
+ connectionHandler,
+ options,
+ name,
+ NO_PARAMETER_TYPES,
+ parsedStatement,
+ originalStatement),
+ NO_PARAMS,
+ ImmutableList.of(),
+ ImmutableList.of());
this.preparedStatement = parse(originalStatement.getSql());
}
@@ -149,14 +160,14 @@ public void executeAsync(BackendConnection backendConnection) {
}
@Override
- public Future describeAsync(BackendConnection backendConnection) {
+ public Future describeAsync(BackendConnection backendConnection) {
// Return null to indicate that this PREPARE statement does not return any
// RowDescriptionResponse.
return Futures.immediateFuture(null);
}
@Override
- public IntermediatePortalStatement bind(
+ public IntermediatePortalStatement createPortal(
String name,
byte[][] parameters,
List parameterFormatCodes,
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/BindMessage.java b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/BindMessage.java
index a7b0ef71a5..9f85261450 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/BindMessage.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/BindMessage.java
@@ -17,6 +17,7 @@
import com.google.api.core.InternalApi;
import com.google.cloud.spanner.pgadapter.ConnectionHandler;
import com.google.cloud.spanner.pgadapter.statements.BackendConnection;
+import com.google.cloud.spanner.pgadapter.statements.IntermediatePortalStatement;
import com.google.cloud.spanner.pgadapter.statements.IntermediatePreparedStatement;
import com.google.cloud.spanner.pgadapter.wireoutput.BindCompleteResponse;
import com.google.common.base.Preconditions;
@@ -39,7 +40,7 @@ public class BindMessage extends AbstractQueryProtocolMessage {
private final List formatCodes;
private final List resultFormatCodes;
private final byte[][] parameters;
- private final IntermediatePreparedStatement statement;
+ private final IntermediatePortalStatement statement;
/** Constructor for Bind messages that are received from the front-end. */
public BindMessage(ConnectionHandler connection) throws Exception {
@@ -49,7 +50,11 @@ public BindMessage(ConnectionHandler connection) throws Exception {
this.formatCodes = getFormatCodes(this.inputStream);
this.parameters = getParameters(this.inputStream);
this.resultFormatCodes = getFormatCodes(this.inputStream);
- this.statement = connection.getStatement(this.statementName);
+ IntermediatePreparedStatement statement = connection.getStatement(statementName);
+ this.statement =
+ statement.createPortal(
+ this.portalName, this.parameters, this.formatCodes, this.resultFormatCodes);
+ this.connection.registerPortal(this.portalName, this.statement);
}
/** Constructor for Bind messages that are constructed to execute a Query message. */
@@ -69,7 +74,11 @@ public BindMessage(
this.formatCodes = ImmutableList.of();
this.resultFormatCodes = ImmutableList.of();
this.parameters = Preconditions.checkNotNull(parameters);
- this.statement = connection.getStatement(statementName);
+ IntermediatePreparedStatement statement = connection.getStatement(statementName);
+ this.statement =
+ statement.createPortal(
+ this.portalName, this.parameters, this.formatCodes, this.resultFormatCodes);
+ this.connection.registerPortal(this.portalName, this.statement);
}
boolean hasParameterValues() {
@@ -78,21 +87,19 @@ boolean hasParameterValues() {
@Override
void buffer(BackendConnection backendConnection) {
- if (isExtendedProtocol() && hasParameterValues() && !this.statement.isDescribed()) {
+ if (isExtendedProtocol() && !this.statement.getPreparedStatement().isDescribed()) {
try {
// Make sure all parameters have been described, so we always send typed parameters to Cloud
// Spanner.
- this.statement.describeParameters(this.parameters, true);
+ this.statement
+ .getPreparedStatement()
+ .autoDescribeParameters(this.parameters, backendConnection);
} catch (Throwable ignore) {
// Ignore any error messages while describing the parameters, and let the following
// DescribePortal or execute message handle any errors that are caused by invalid
// statements.
}
}
- this.connection.registerPortal(
- this.portalName,
- this.statement.bind(
- this.portalName, this.parameters, this.formatCodes, this.resultFormatCodes));
}
@Override
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/DescribeMessage.java b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/DescribeMessage.java
index 92b1e6567e..4edc9ba5bd 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/DescribeMessage.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/DescribeMessage.java
@@ -16,12 +16,12 @@
import com.google.api.core.InternalApi;
import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.cloud.spanner.connection.StatementResult;
import com.google.cloud.spanner.pgadapter.ConnectionHandler;
import com.google.cloud.spanner.pgadapter.error.PGExceptionFactory;
-import com.google.cloud.spanner.pgadapter.metadata.DescribePortalMetadata;
-import com.google.cloud.spanner.pgadapter.metadata.DescribeStatementMetadata;
+import com.google.cloud.spanner.pgadapter.metadata.DescribeResult;
import com.google.cloud.spanner.pgadapter.statements.BackendConnection;
-import com.google.cloud.spanner.pgadapter.statements.IntermediatePreparedStatement;
+import com.google.cloud.spanner.pgadapter.statements.IntermediateStatement;
import com.google.cloud.spanner.pgadapter.wireoutput.NoDataResponse;
import com.google.cloud.spanner.pgadapter.wireoutput.ParameterDescriptionResponse;
import com.google.cloud.spanner.pgadapter.wireoutput.RowDescriptionResponse;
@@ -38,8 +38,8 @@ public class DescribeMessage extends AbstractQueryProtocolMessage {
private final PreparedType type;
private final String name;
- private final IntermediatePreparedStatement statement;
- private Future describePortalMetadata;
+ private final IntermediateStatement statement;
+ private Future describePortalMetadata;
public DescribeMessage(ConnectionHandler connection) throws Exception {
super(connection);
@@ -77,10 +77,9 @@ public DescribeMessage(
@Override
void buffer(BackendConnection backendConnection) {
if (this.type == PreparedType.Portal && this.statement.containsResultSet()) {
- describePortalMetadata =
- (Future) this.statement.describeAsync(backendConnection);
+ describePortalMetadata = this.statement.describeAsync(backendConnection);
} else if (this.type == PreparedType.Statement) {
- this.statement.setDescribed();
+ describePortalMetadata = this.statement.describeAsync(backendConnection);
}
}
@@ -148,7 +147,7 @@ void handleDescribePortal() throws Exception {
new RowDescriptionResponse(
this.outputStream,
this.statement,
- getPortalMetadata().getMetadata(),
+ getPortalMetadata().getResultSet(),
this.connection.getServer().getOptions(),
this.queryMode)
.send(false);
@@ -167,7 +166,7 @@ void handleDescribePortal() throws Exception {
}
@VisibleForTesting
- DescribePortalMetadata getPortalMetadata() {
+ StatementResult getPortalMetadata() {
if (!this.describePortalMetadata.isDone()) {
throw new IllegalStateException("Trying to get Portal Metadata before it has been described");
}
@@ -186,11 +185,13 @@ DescribePortalMetadata getPortalMetadata() {
* @throws Exception if sending the message back to the client causes an error.
*/
public void handleDescribeStatement() throws Exception {
- try (DescribeStatementMetadata metadata =
- (DescribeStatementMetadata) this.statement.describe()) {
+ if (this.statement.hasException()) {
+ throw this.statement.getException();
+ } else {
if (isExtendedProtocol()) {
+ DescribeResult metadata = this.statement.describe();
new ParameterDescriptionResponse(this.outputStream, metadata.getParameters()).send(false);
- if (metadata.getResultSet() != null) {
+ if (metadata.getResultSet() != null && metadata.getResultSet().getColumnCount() > 0) {
new RowDescriptionResponse(
this.outputStream,
this.statement,
@@ -202,8 +203,6 @@ public void handleDescribeStatement() throws Exception {
new NoDataResponse(this.outputStream).send(false);
}
}
- } catch (Exception exception) {
- this.handleError(exception);
}
}
}
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ParseMessage.java b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ParseMessage.java
index f986ab41d7..48957a87bc 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ParseMessage.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ParseMessage.java
@@ -123,15 +123,13 @@ static IntermediatePreparedStatement createStatement(
parsedStatement,
originalStatement);
} else {
- IntermediatePreparedStatement statement =
- new IntermediatePreparedStatement(
- connectionHandler,
- connectionHandler.getServer().getOptions(),
- name,
- parsedStatement,
- originalStatement);
- statement.setParameterDataTypes(parameterDataTypes);
- return statement;
+ return new IntermediatePreparedStatement(
+ connectionHandler,
+ connectionHandler.getServer().getOptions(),
+ name,
+ parameterDataTypes,
+ parsedStatement,
+ originalStatement);
}
} catch (Exception exception) {
return new InvalidStatement(
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/AbortedMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/AbortedMockServerTest.java
index 3662376e09..3416b3326f 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/AbortedMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/AbortedMockServerTest.java
@@ -39,6 +39,7 @@
import com.google.cloud.spanner.connection.RandomResultSetGenerator;
import com.google.common.collect.ImmutableList;
import com.google.spanner.v1.ExecuteSqlRequest;
+import com.google.spanner.v1.ResultSetStats;
import com.google.spanner.v1.TypeCode;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Status;
@@ -561,12 +562,14 @@ public void testDescribeDmlWithNonExistingTable() throws SQLException {
@Test
public void testDescribeDmlWithSchemaPrefix() throws SQLException {
String sql = "update public.my_table set value=? where id=?";
- String describeSql = "select $1, $2 from (select value=$1 from public.my_table where id=$2) p";
+ String describeSql = "update public.my_table set value=$1 where id=$2";
mockSpanner.putStatementResult(
StatementResult.query(
Statement.of(describeSql),
com.google.spanner.v1.ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.STRING, TypeCode.INT64)))
+ .setMetadata(
+ createParameterTypesMetadata(ImmutableList.of(TypeCode.STRING, TypeCode.INT64)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
try (Connection connection = createConnection()) {
try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
@@ -580,13 +583,14 @@ public void testDescribeDmlWithSchemaPrefix() throws SQLException {
@Test
public void testDescribeDmlWithQuotedSchemaPrefix() throws SQLException {
String sql = "update \"public\".\"my_table\" set value=? where id=?";
- String describeSql =
- "select $1, $2 from (select value=$1 from \"public\".\"my_table\" where id=$2) p";
+ String describeSql = "update \"public\".\"my_table\" set value=$1 where id=$2";
mockSpanner.putStatementResult(
StatementResult.query(
Statement.of(describeSql),
com.google.spanner.v1.ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.STRING, TypeCode.INT64)))
+ .setMetadata(
+ createParameterTypesMetadata(ImmutableList.of(TypeCode.STRING, TypeCode.INT64)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
try (Connection connection = createConnection()) {
try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java
index 785d00389f..7b6180323f 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/AbstractMockServerTest.java
@@ -312,6 +312,22 @@ protected static ResultSetMetadata createMetadata(ImmutableList types)
return ResultSetMetadata.newBuilder().setRowType(builder.build()).build();
}
+ protected static ResultSetMetadata createParameterTypesMetadata(ImmutableList types) {
+ StructType.Builder builder = StructType.newBuilder();
+ for (int index = 0; index < types.size(); index++) {
+ builder.addFields(
+ Field.newBuilder()
+ .setType(
+ Type.newBuilder()
+ .setCode(types.get(index))
+ .setTypeAnnotation(getTypeAnnotationCode(types.get(index)))
+ .build())
+ .setName("p" + (index + 1))
+ .build());
+ }
+ return ResultSetMetadata.newBuilder().setUndeclaredParameters(builder.build()).build();
+ }
+
protected static ResultSetMetadata createMetadata(
ImmutableList types, ImmutableList names) {
Preconditions.checkArgument(
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcDescribeStatementTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcDescribeStatementTest.java
index 5745f74404..98fbf45c0f 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcDescribeStatementTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcDescribeStatementTest.java
@@ -189,7 +189,7 @@ public void testParameterMetaData() throws SQLException {
+ "and col_date=? "
+ "and col_varchar=? "
+ "and col_jsonb=?",
- "update all_types set col_bigint=?, "
+ "update all_types set "
+ "col_bool=?, "
+ "col_bytea=?, "
+ "col_float8=?, "
@@ -199,7 +199,7 @@ public void testParameterMetaData() throws SQLException {
+ "col_date=?, "
+ "col_varchar=?, "
+ "col_jsonb=?",
- "update all_types set col_bigint=null, "
+ "update all_types set "
+ "col_bool=null, "
+ "col_bytea=null, "
+ "col_float8=null, "
@@ -235,22 +235,28 @@ public void testParameterMetaData() throws SQLException {
try (Connection connection = DriverManager.getConnection(getConnectionUrl())) {
try (PreparedStatement statement = connection.prepareStatement(sql)) {
ParameterMetaData metadata = statement.getParameterMetaData();
- assertEquals(10, metadata.getParameterCount());
+ if (sql.startsWith("update all_types set col_bool=?,")) {
+ assertEquals(sql, 9, metadata.getParameterCount());
+ } else {
+ assertEquals(sql, 10, metadata.getParameterCount());
+ }
for (int index = 1; index <= metadata.getParameterCount(); index++) {
assertEquals(ParameterMetaData.parameterModeIn, metadata.getParameterMode(index));
assertEquals(ParameterMetaData.parameterNullableUnknown, metadata.isNullable(index));
}
int index = 0;
+ if (metadata.getParameterCount() == 10) {
+ assertEquals(sql, Types.BIGINT, metadata.getParameterType(++index));
+ }
+ assertEquals(sql, Types.BIT, metadata.getParameterType(++index));
+ assertEquals(sql, Types.BINARY, metadata.getParameterType(++index));
+ assertEquals(sql, Types.DOUBLE, metadata.getParameterType(++index));
assertEquals(sql, Types.BIGINT, metadata.getParameterType(++index));
- assertEquals(Types.BIT, metadata.getParameterType(++index));
- assertEquals(Types.BINARY, metadata.getParameterType(++index));
- assertEquals(Types.DOUBLE, metadata.getParameterType(++index));
- assertEquals(Types.BIGINT, metadata.getParameterType(++index));
- assertEquals(Types.NUMERIC, metadata.getParameterType(++index));
- assertEquals(Types.TIMESTAMP, metadata.getParameterType(++index));
- assertEquals(Types.DATE, metadata.getParameterType(++index));
- assertEquals(Types.VARCHAR, metadata.getParameterType(++index));
- assertEquals(Types.VARCHAR, metadata.getParameterType(++index));
+ assertEquals(sql, Types.NUMERIC, metadata.getParameterType(++index));
+ assertEquals(sql, Types.TIMESTAMP, metadata.getParameterType(++index));
+ assertEquals(sql, Types.DATE, metadata.getParameterType(++index));
+ assertEquals(sql, Types.VARCHAR, metadata.getParameterType(++index));
+ assertEquals(sql, Types.VARCHAR, metadata.getParameterType(++index));
}
}
}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
index 3add6fa7b1..e9f1d85e3a 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
@@ -49,7 +49,11 @@
import com.google.spanner.v1.ExecuteBatchDmlRequest;
import com.google.spanner.v1.ExecuteSqlRequest;
import com.google.spanner.v1.ExecuteSqlRequest.QueryMode;
+import com.google.spanner.v1.ResultSetMetadata;
+import com.google.spanner.v1.ResultSetStats;
import com.google.spanner.v1.RollbackRequest;
+import com.google.spanner.v1.StructType;
+import com.google.spanner.v1.StructType.Field;
import com.google.spanner.v1.Type;
import com.google.spanner.v1.TypeAnnotationCode;
import com.google.spanner.v1.TypeCode;
@@ -154,6 +158,55 @@ public void testQuery() throws SQLException {
}
}
+ @Test
+ public void testPreparedStatementParameterMetadata() throws SQLException {
+ String sql = "SELECT * FROM foo WHERE id=? or value=?";
+ String pgSql = "SELECT * FROM foo WHERE id=$1 or value=$2";
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of(pgSql),
+ com.google.spanner.v1.ResultSet.newBuilder()
+ .setMetadata(
+ ResultSetMetadata.newBuilder()
+ .setRowType(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("col1")
+ .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
+ .build())
+ .addFields(
+ Field.newBuilder()
+ .setName("col2")
+ .setType(Type.newBuilder().setCode(TypeCode.STRING).build())
+ .build())
+ .build())
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
+ .build())
+ .addFields(
+ Field.newBuilder()
+ .setName("p2")
+ .setType(Type.newBuilder().setCode(TypeCode.STRING).build())
+ .build())
+ .build())
+ .build())
+ .build()));
+
+ try (Connection connection = DriverManager.getConnection(createUrl())) {
+ try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
+ ParameterMetaData parameters = preparedStatement.getParameterMetaData();
+ assertEquals(2, parameters.getParameterCount());
+ assertEquals(Types.BIGINT, parameters.getParameterType(1));
+ assertEquals(Types.VARCHAR, parameters.getParameterType(2));
+ }
+ }
+ }
+
@Test
public void testInvalidQuery() throws SQLException {
String sql = "/ not a valid comment / SELECT 1";
@@ -493,42 +546,63 @@ public void testQueryWithLegacyDateParameter() throws SQLException {
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
// Prepare threshold less than 0 means use binary transfer + DESCRIBE statement.
// However, the legacy date type will never use BINARY transfer and will always be sent with
- // unspecified type by the JDBC driver the first time. This means that we need 3 round trips
- // for a query that uses a prepared statement the first time.
- int expectedRequestCount;
- switch (preparedThreshold) {
- case -1:
- case 1:
- expectedRequestCount = 3;
- break;
- default:
- expectedRequestCount = 2;
- break;
- }
+ // unspecified type by the JDBC driver the first time. This means that we need 2 round trips
+ // in all cases, as the statement will either use an explicit DESCRIBE message, or it will
+ // be auto-described by PGAdapter.
+ int expectedRequestCount = 2;
assertEquals(
"Prepare threshold: " + preparedThreshold, expectedRequestCount, requests.size());
- ExecuteSqlRequest executeRequest;
- if (preparedThreshold == 1) {
- // The order of statements here is a little strange. The execution of the statement is
- // executed first, and the describe statements are then executed afterwards. The reason
- // for this is that JDBC does the following when it encounters a statement parameter that
- // is 'unknown' (it considers the legacy date type as unknown, as it does not know if the
- // user means date, timestamp or timestamptz):
- // 1. It sends a DescribeStatement message, but without a flush or a sync, as it is not
- // planning on using the information for this request.
- // 2. It then sends the Execute message followed by a sync. This causes PGAdapter to sync
- // the backend connection and execute everything in the actual execution pipeline.
- // 3. PGAdapter then executes anything left in the message queue. The DescribeMessage is
- // still there, and is therefore executed after the Execute message.
- // All the above still works as intended, as the responses are sent in the expected order.
- executeRequest = requests.get(0);
- for (int i = 1; i < requests.size(); i++) {
- assertEquals(QueryMode.PLAN, requests.get(i).getQueryMode());
+ ExecuteSqlRequest executeRequest = requests.get(requests.size() - 1);
+ assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
+ assertEquals(pgSql, executeRequest.getSql());
+
+ Map params = executeRequest.getParams().getFieldsMap();
+ Map types = executeRequest.getParamTypesMap();
+
+ assertEquals(TypeCode.DATE, types.get("p1").getCode());
+ assertEquals("2022-03-29", params.get("p1").getStringValue());
+
+ mockSpanner.clearRequests();
+ }
+ }
+ }
+
+ @Test
+ public void testAutoDescribedStatementsAreReused() throws SQLException {
+ String jdbcSql = "select col_date from all_types where col_date=?";
+ String pgSql = "select col_date from all_types where col_date=$1";
+ mockSpanner.putStatementResult(StatementResult.query(Statement.of(pgSql), ALL_TYPES_RESULTSET));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.newBuilder(pgSql).bind("p1").to(Date.parseDate("2022-03-29")).build(),
+ ALL_TYPES_RESULTSET));
+
+ try (Connection connection = DriverManager.getConnection(createUrl())) {
+ for (int attempt : new int[] {1, 2}) {
+ try (PreparedStatement preparedStatement = connection.prepareStatement(jdbcSql)) {
+ // Threshold 0 means never use a named prepared statement.
+ preparedStatement.unwrap(PgStatement.class).setPrepareThreshold(0);
+ preparedStatement.setDate(1, new java.sql.Date(2022 - 1900, Calendar.MARCH, 29));
+ try (ResultSet resultSet = preparedStatement.executeQuery()) {
+ assertTrue(resultSet.next());
+ assertEquals(
+ new java.sql.Date(2022 - 1900, Calendar.MARCH, 29), resultSet.getDate("col_date"));
+ assertFalse(resultSet.next());
}
+ }
+
+ // The first time we execute this statement the number of requests should be 2, as the
+ // statement is auto-described by the backend. The second time we execute the statement the
+ // backend should reuse the result from the first auto-describe roundtrip.
+ List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
+ if (attempt == 1) {
+ assertEquals(2, requests.size());
} else {
- executeRequest = requests.get(requests.size() - 1);
+ assertEquals(1, requests.size());
}
+
+ ExecuteSqlRequest executeRequest = requests.get(requests.size() - 1);
assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
assertEquals(pgSql, executeRequest.getSql());
@@ -679,12 +753,13 @@ public void testDmlWithNonExistingTable() throws SQLException {
@Test
public void testNullValues() throws SQLException {
+ String pgSql =
+ "insert into all_types "
+ + "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar) "
+ + "values ($1, $2, $3, $4, $5, $6, $7, $8, $9)";
mockSpanner.putStatementResult(
StatementResult.update(
- Statement.newBuilder(
- "insert into all_types "
- + "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar) "
- + "values ($1, $2, $3, $4, $5, $6, $7, $8, $9)")
+ Statement.newBuilder(pgSql)
.bind("p1")
.to(2L)
.bind("p2")
@@ -799,14 +874,6 @@ public void testDescribeDmlWithNonExistingTable() throws SQLException {
Status.NOT_FOUND
.withDescription("Table non_existing_table not found")
.asRuntimeException()));
- String describeSql =
- "select $1, $2 from (select value=$2 from non_existing_table where id=$1) p";
- mockSpanner.putStatementResult(
- StatementResult.exception(
- Statement.of(describeSql),
- Status.NOT_FOUND
- .withDescription("Table non_existing_table not found")
- .asRuntimeException()));
try (Connection connection = DriverManager.getConnection(createUrl())) {
try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
SQLException exception =
@@ -815,27 +882,25 @@ public void testDescribeDmlWithNonExistingTable() throws SQLException {
}
}
- // We receive two ExecuteSql requests:
+ // We receive one ExecuteSql requests:
// 1. DescribeStatement (parameters). This statement fails as the table does not exist.
- // 2. Because the DescribeStatement step fails, PGAdapter executes the DML statement in analyze
- // mode to force a 'correct' error message.
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- assertEquals(2, requests.size());
- assertEquals(describeSql, requests.get(0).getSql());
+ assertEquals(1, requests.size());
+ assertEquals(sql, requests.get(0).getSql());
assertEquals(QueryMode.PLAN, requests.get(0).getQueryMode());
- assertEquals(sql, requests.get(1).getSql());
- assertEquals(QueryMode.PLAN, requests.get(1).getQueryMode());
}
@Test
public void testDescribeDmlWithSchemaPrefix() throws SQLException {
String sql = "update public.my_table set value=? where id=?";
- String describeSql = "select $1, $2 from (select value=$1 from public.my_table where id=$2) p";
+ String pgSql = "update public.my_table set value=$1 where id=$2";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(pgSql),
com.google.spanner.v1.ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.STRING, TypeCode.INT64)))
+ .setMetadata(
+ createParameterTypesMetadata(ImmutableList.of(TypeCode.STRING, TypeCode.INT64)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
try (Connection connection = DriverManager.getConnection(createUrl())) {
try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
@@ -847,20 +912,21 @@ public void testDescribeDmlWithSchemaPrefix() throws SQLException {
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
assertEquals(1, requests.size());
- assertEquals(describeSql, requests.get(0).getSql());
+ assertEquals(pgSql, requests.get(0).getSql());
assertEquals(QueryMode.PLAN, requests.get(0).getQueryMode());
}
@Test
public void testDescribeDmlWithQuotedSchemaPrefix() throws SQLException {
String sql = "update \"public\".\"my_table\" set value=? where id=?";
- String describeSql =
- "select $1, $2 from (select value=$1 from \"public\".\"my_table\" where id=$2) p";
+ String pgSql = "update \"public\".\"my_table\" set value=$1 where id=$2";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(pgSql),
com.google.spanner.v1.ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.STRING, TypeCode.INT64)))
+ .setMetadata(
+ createParameterTypesMetadata(ImmutableList.of(TypeCode.STRING, TypeCode.INT64)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
try (Connection connection = DriverManager.getConnection(createUrl())) {
try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
@@ -872,7 +938,7 @@ public void testDescribeDmlWithQuotedSchemaPrefix() throws SQLException {
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
assertEquals(1, requests.size());
- assertEquals(describeSql, requests.get(0).getSql());
+ assertEquals(pgSql, requests.get(0).getSql());
assertEquals(QueryMode.PLAN, requests.get(0).getQueryMode());
}
@@ -2806,28 +2872,41 @@ public void testReplacePgCatalogTablesOff() throws SQLException {
public void testDescribeStatementWithMoreThan50Parameters() throws SQLException {
try (Connection connection = DriverManager.getConnection(createUrl())) {
// Force binary transfer + usage of server-side prepared statements.
- connection.unwrap(PGConnection.class).setPrepareThreshold(-1);
+ connection.unwrap(PGConnection.class).setPrepareThreshold(1);
String sql =
String.format(
"insert into foo values (%s)",
IntStream.rangeClosed(1, 51).mapToObj(i -> "?").collect(Collectors.joining(",")));
+ String pgSql =
+ String.format(
+ "insert into foo values (%s)",
+ IntStream.rangeClosed(1, 51).mapToObj(i -> "$" + i).collect(Collectors.joining(",")));
+ ImmutableList typeCodes =
+ ImmutableList.copyOf(
+ IntStream.rangeClosed(1, 51)
+ .mapToObj(i -> TypeCode.STRING)
+ .collect(Collectors.toList()));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of(pgSql),
+ com.google.spanner.v1.ResultSet.newBuilder()
+ .setMetadata(createParameterTypesMetadata(typeCodes))
+ .setStats(ResultSetStats.newBuilder().build())
+ .build()));
try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
- SQLException sqlException =
- assertThrows(SQLException.class, preparedStatement::getParameterMetaData);
- assertEquals(
- "ERROR: Cannot describe statements with more than 50 parameters",
- sqlException.getMessage());
+ ParameterMetaData metadata = preparedStatement.getParameterMetaData();
+ assertEquals(51, metadata.getParameterCount());
}
+ Statement.Builder builder = Statement.newBuilder(pgSql);
+ IntStream.rangeClosed(1, 51).forEach(i -> builder.bind("p" + i).to((String) null));
+ Statement statement = builder.build();
+ mockSpanner.putStatementResult(StatementResult.update(statement, 1L));
try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
for (int i = 0; i < 51; i++) {
preparedStatement.setNull(i + 1, Types.NULL);
}
- SQLException sqlException =
- assertThrows(SQLException.class, preparedStatement::executeUpdate);
- assertEquals(
- "ERROR: Cannot describe statements with more than 50 parameters",
- sqlException.getMessage());
+ assertEquals(1, preparedStatement.executeUpdate());
}
}
}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/golang/GormMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/golang/GormMockServerTest.java
index 4e93abcae1..8994b5370c 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/golang/GormMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/golang/GormMockServerTest.java
@@ -34,6 +34,7 @@
import com.google.spanner.v1.ExecuteSqlRequest.QueryMode;
import com.google.spanner.v1.ResultSet;
import com.google.spanner.v1.ResultSetMetadata;
+import com.google.spanner.v1.ResultSetStats;
import com.google.spanner.v1.RollbackRequest;
import com.google.spanner.v1.StructType;
import com.google.spanner.v1.StructType.Field;
@@ -186,13 +187,8 @@ public void testFirst() {
public void testCreateBlogAndUser() {
String insertUserSql =
"INSERT INTO \"users\" (\"id\",\"name\",\"email\",\"age\",\"birthday\",\"member_number\",\"activated_at\",\"created_at\",\"updated_at\") VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)";
- String describeInsertUserSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9 from (select \"id\"=$1, \"name\"=$2, \"email\"=$3, \"age\"=$4, \"birthday\"=$5, \"member_number\"=$6, \"activated_at\"=$7, \"created_at\"=$8, \"updated_at\"=$9 from \"users\") p";
String insertBlogSql =
"INSERT INTO \"blogs\" (\"id\",\"name\",\"description\",\"user_id\",\"created_at\",\"updated_at\") VALUES ($1,$2,$3,$4,$5,$6)";
- String describeInsertBlogSql =
- "select $1, $2, $3, $4, $5, $6 from (select \"id\"=$1, \"name\"=$2, \"description\"=$3, \"user_id\"=$4, \"created_at\"=$5, \"updated_at\"=$6 from \"blogs\") p";
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(insertUserSql), 0L));
mockSpanner.putStatementResult(
StatementResult.update(
Statement.newBuilder(insertUserSql)
@@ -218,10 +214,10 @@ public void testCreateBlogAndUser() {
1L));
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeInsertUserSql),
+ Statement.of(insertUserSql),
ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.STRING,
@@ -232,8 +228,8 @@ public void testCreateBlogAndUser() {
TypeCode.TIMESTAMP,
TypeCode.TIMESTAMP,
TypeCode.TIMESTAMP)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(insertBlogSql), 0L));
mockSpanner.putStatementResult(
StatementResult.update(
Statement.newBuilder(insertBlogSql)
@@ -253,10 +249,10 @@ public void testCreateBlogAndUser() {
1L));
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeInsertBlogSql),
+ Statement.of(insertBlogSql),
ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.STRING,
@@ -264,26 +260,25 @@ public void testCreateBlogAndUser() {
TypeCode.INT64,
TypeCode.TIMESTAMP,
TypeCode.TIMESTAMP)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
String res = gormTest.TestCreateBlogAndUser(createConnString());
assertNull(res);
assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
- assertEquals(6, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
+ assertEquals(4, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- assertEquals(describeInsertUserSql, requests.get(0).getSql());
+ assertEquals(insertUserSql, requests.get(0).getSql());
+ assertEquals(QueryMode.PLAN, requests.get(0).getQueryMode());
assertEquals(insertUserSql, requests.get(1).getSql());
- assertEquals(QueryMode.PLAN, requests.get(1).getQueryMode());
- assertEquals(insertUserSql, requests.get(2).getSql());
- assertEquals(QueryMode.NORMAL, requests.get(2).getQueryMode());
-
- assertEquals(describeInsertBlogSql, requests.get(3).getSql());
- assertEquals(insertBlogSql, requests.get(4).getSql());
- assertEquals(QueryMode.PLAN, requests.get(4).getQueryMode());
- assertEquals(insertBlogSql, requests.get(5).getSql());
- assertEquals(QueryMode.NORMAL, requests.get(5).getQueryMode());
+ assertEquals(QueryMode.NORMAL, requests.get(1).getQueryMode());
+
+ assertEquals(insertBlogSql, requests.get(2).getSql());
+ assertEquals(QueryMode.PLAN, requests.get(2).getQueryMode());
+ assertEquals(insertBlogSql, requests.get(3).getSql());
+ assertEquals(QueryMode.NORMAL, requests.get(3).getQueryMode());
}
@Test
@@ -295,9 +290,7 @@ public void testQueryAllDataTypes() {
assertNull(res);
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- // pgx by default always uses prepared statements. As this statement does not contain any
- // parameters, we don't need to describe the parameter types, so it is 'only' sent twice to the
- // backend.
+ // pgx by default always uses prepared statements.
assertEquals(2, requests.size());
ExecuteSqlRequest describeRequest = requests.get(0);
assertEquals(sql, describeRequest.getSql());
@@ -320,9 +313,7 @@ public void testQueryNullsAllDataTypes() {
assertNull(res);
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- // pgx by default always uses prepared statements. As this statement does not contain any
- // parameters, we don't need to describe the parameter types, so it is 'only' sent twice to the
- // backend.
+ // pgx by default always uses prepared statements.
assertEquals(2, requests.size());
ExecuteSqlRequest describeRequest = requests.get(0);
assertEquals(sql, describeRequest.getSql());
@@ -341,15 +332,12 @@ public void testInsertAllDataTypes() {
"INSERT INTO \"all_types\" "
+ "(\"col_bigint\",\"col_bool\",\"col_bytea\",\"col_float8\",\"col_int\",\"col_numeric\",\"col_timestamptz\",\"col_date\",\"col_varchar\") "
+ "VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)";
- String describeSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9 "
- + "from (select \"col_bigint\"=$1, \"col_bool\"=$2, \"col_bytea\"=$3, \"col_float8\"=$4, \"col_int\"=$5, \"col_numeric\"=$6, \"col_timestamptz\"=$7, \"col_date\"=$8, \"col_varchar\"=$9 from \"all_types\") p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(sql),
ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.BOOL,
@@ -360,8 +348,8 @@ public void testInsertAllDataTypes() {
TypeCode.TIMESTAMP,
TypeCode.DATE,
TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 0L));
mockSpanner.putStatementResult(
StatementResult.update(
Statement.newBuilder(sql)
@@ -390,31 +378,23 @@ public void testInsertAllDataTypes() {
assertNull(res);
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- // pgx by default always uses prepared statements. That means that each request is sent three
+ // pgx by default always uses prepared statements. That means that each request is sent two
// times to the backend the first time it is executed:
- // 1. DescribeStatement (parameters)
- // 2. DescribeStatement (verify validity / PARSE) -- This step could be skipped.
- // 3. Execute
- assertEquals(3, requests.size());
- ExecuteSqlRequest describeParamsRequest = requests.get(0);
- assertEquals(describeSql, describeParamsRequest.getSql());
- assertEquals(QueryMode.PLAN, describeParamsRequest.getQueryMode());
- assertTrue(describeParamsRequest.hasTransaction());
- assertTrue(describeParamsRequest.getTransaction().hasBegin());
- assertTrue(describeParamsRequest.getTransaction().getBegin().hasReadWrite());
-
- ExecuteSqlRequest describeRequest = requests.get(1);
+ // 1. DescribeStatement
+ // 2. Execute
+ assertEquals(2, requests.size());
+ ExecuteSqlRequest describeRequest = requests.get(0);
assertEquals(sql, describeRequest.getSql());
assertEquals(QueryMode.PLAN, describeRequest.getQueryMode());
assertTrue(describeRequest.hasTransaction());
- assertTrue(describeRequest.getTransaction().hasId());
+ assertTrue(describeRequest.getTransaction().hasBegin());
+ assertTrue(describeRequest.getTransaction().getBegin().hasReadWrite());
- ExecuteSqlRequest executeRequest = requests.get(2);
+ ExecuteSqlRequest executeRequest = requests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
assertTrue(executeRequest.hasTransaction());
assertTrue(executeRequest.getTransaction().hasId());
- assertEquals(describeRequest.getTransaction().getId(), executeRequest.getTransaction().getId());
assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
CommitRequest commitRequest = mockSpanner.getRequestsOfType(CommitRequest.class).get(0);
@@ -427,15 +407,12 @@ public void testInsertNullsAllDataTypes() {
"INSERT INTO \"all_types\" "
+ "(\"col_bigint\",\"col_bool\",\"col_bytea\",\"col_float8\",\"col_int\",\"col_numeric\",\"col_timestamptz\",\"col_date\",\"col_varchar\") "
+ "VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)";
- String describeSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9 "
- + "from (select \"col_bigint\"=$1, \"col_bool\"=$2, \"col_bytea\"=$3, \"col_float8\"=$4, \"col_int\"=$5, \"col_numeric\"=$6, \"col_timestamptz\"=$7, \"col_date\"=$8, \"col_varchar\"=$9 from \"all_types\") p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(sql),
ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.BOOL,
@@ -446,8 +423,8 @@ public void testInsertNullsAllDataTypes() {
TypeCode.TIMESTAMP,
TypeCode.DATE,
TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 0L));
mockSpanner.putStatementResult(
StatementResult.update(
Statement.newBuilder(sql)
@@ -476,19 +453,15 @@ public void testInsertNullsAllDataTypes() {
assertNull(res);
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- // pgx by default always uses prepared statements. That means that each request is sent three
+ // pgx by default always uses prepared statements. That means that each request is sent two
// times to the backend the first time it is executed:
- // 1. DescribeStatement (parameters)
- // 2. DescribeStatement (verify validity / PARSE) -- This step could be skipped.
- // 3. Execute
- assertEquals(3, requests.size());
- ExecuteSqlRequest describeParamsRequest = requests.get(0);
- assertEquals(describeSql, describeParamsRequest.getSql());
- assertEquals(QueryMode.PLAN, describeParamsRequest.getQueryMode());
- ExecuteSqlRequest describeRequest = requests.get(1);
+ // 1. DescribeStatement
+ // 2. Execute
+ assertEquals(2, requests.size());
+ ExecuteSqlRequest describeRequest = requests.get(0);
assertEquals(sql, describeRequest.getSql());
assertEquals(QueryMode.PLAN, describeRequest.getQueryMode());
- ExecuteSqlRequest executeRequest = requests.get(2);
+ ExecuteSqlRequest executeRequest = requests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
}
@@ -497,15 +470,12 @@ public void testInsertNullsAllDataTypes() {
public void testUpdateAllDataTypes() {
String sql =
"UPDATE \"all_types\" SET \"col_bigint\"=$1,\"col_bool\"=$2,\"col_bytea\"=$3,\"col_float8\"=$4,\"col_int\"=$5,\"col_numeric\"=$6,\"col_timestamptz\"=$7,\"col_date\"=$8,\"col_varchar\"=$9 WHERE \"col_varchar\" = $10";
- String describeSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 from "
- + "(select \"col_bigint\"=$1, \"col_bool\"=$2, \"col_bytea\"=$3, \"col_float8\"=$4, \"col_int\"=$5, \"col_numeric\"=$6, \"col_timestamptz\"=$7, \"col_date\"=$8, \"col_varchar\"=$9 from \"all_types\" WHERE \"col_varchar\" = $10) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(sql),
ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.BOOL,
@@ -517,8 +487,8 @@ public void testUpdateAllDataTypes() {
TypeCode.DATE,
TypeCode.STRING,
TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 0L));
mockSpanner.putStatementResult(
StatementResult.update(
Statement.newBuilder(sql)
@@ -549,19 +519,15 @@ public void testUpdateAllDataTypes() {
assertNull(res);
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- // pgx by default always uses prepared statements. That means that each request is sent three
+ // pgx by default always uses prepared statements. That means that each request is sent two
// times to the backend the first time it is executed:
- // 1. DescribeStatement (parameters)
- // 2. DescribeStatement (verify validity / PARSE) -- This step could be skipped.
- // 3. Execute
- assertEquals(3, requests.size());
- ExecuteSqlRequest describeParamsRequest = requests.get(0);
- assertEquals(describeSql, describeParamsRequest.getSql());
- assertEquals(QueryMode.PLAN, describeParamsRequest.getQueryMode());
- ExecuteSqlRequest describeRequest = requests.get(1);
+ // 1. DescribeStatement
+ // 2. Execute
+ assertEquals(2, requests.size());
+ ExecuteSqlRequest describeRequest = requests.get(0);
assertEquals(sql, describeRequest.getSql());
assertEquals(QueryMode.PLAN, describeRequest.getQueryMode());
- ExecuteSqlRequest executeRequest = requests.get(2);
+ ExecuteSqlRequest executeRequest = requests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
}
@@ -569,15 +535,13 @@ public void testUpdateAllDataTypes() {
@Test
public void testDelete() {
String sql = "DELETE FROM \"all_types\" WHERE \"all_types\".\"col_varchar\" = $1";
- String describeSql =
- "select $1 from (select 1 from \"all_types\" WHERE \"all_types\".\"col_varchar\" = $1) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(sql),
ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setMetadata(createParameterTypesMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 0L));
mockSpanner.putStatementResult(
StatementResult.update(Statement.newBuilder(sql).bind("p1").to("test_string").build(), 1L));
@@ -587,17 +551,13 @@ public void testDelete() {
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
// pgx by default always uses prepared statements. That means that each request is sent three
// times to the backend the first time it is executed:
- // 1. DescribeStatement (parameters)
- // 2. DescribeStatement (verify validity / PARSE) -- This step could be skipped.
- // 3. Execute
- assertEquals(3, requests.size());
- ExecuteSqlRequest describeParamsRequest = requests.get(0);
- assertEquals(describeSql, describeParamsRequest.getSql());
- assertEquals(QueryMode.PLAN, describeParamsRequest.getQueryMode());
- ExecuteSqlRequest describeRequest = requests.get(1);
+ // 1. DescribeStatement
+ // 2. Execute
+ assertEquals(2, requests.size());
+ ExecuteSqlRequest describeRequest = requests.get(0);
assertEquals(sql, describeRequest.getSql());
assertEquals(QueryMode.PLAN, describeRequest.getQueryMode());
- ExecuteSqlRequest executeRequest = requests.get(2);
+ ExecuteSqlRequest executeRequest = requests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
}
@@ -610,15 +570,12 @@ public void testCreateInBatches() {
+ "($1,$2,$3,$4,$5,$6,$7,$8,$9),"
+ "($10,$11,$12,$13,$14,$15,$16,$17,$18),"
+ "($19,$20,$21,$22,$23,$24,$25,$26,$27)";
- String describeSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27 from "
- + "(select \"col_bigint\"=$1, \"col_bool\"=$2, \"col_bytea\"=$3, \"col_float8\"=$4, \"col_int\"=$5, \"col_numeric\"=$6, \"col_timestamptz\"=$7, \"col_date\"=$8, \"col_varchar\"=$9, \"col_bigint\"=$10, \"col_bool\"=$11, \"col_bytea\"=$12, \"col_float8\"=$13, \"col_int\"=$14, \"col_numeric\"=$15, \"col_timestamptz\"=$16, \"col_date\"=$17, \"col_varchar\"=$18, \"col_bigint\"=$19, \"col_bool\"=$20, \"col_bytea\"=$21, \"col_float8\"=$22, \"col_int\"=$23, \"col_numeric\"=$24, \"col_timestamptz\"=$25, \"col_date\"=$26, \"col_varchar\"=$27 from \"all_types\") p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(sql),
ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.BOOL,
@@ -647,8 +604,8 @@ public void testCreateInBatches() {
TypeCode.TIMESTAMP,
TypeCode.DATE,
TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 0L));
mockSpanner.putStatementResult(
StatementResult.update(
Statement.newBuilder(sql)
@@ -713,19 +670,15 @@ public void testCreateInBatches() {
assertNull(res);
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- // pgx by default always uses prepared statements. That means that each request is sent three
+ // pgx by default always uses prepared statements. That means that each request is sent two
// times to the backend the first time it is executed:
- // 1. DescribeStatement (parameters)
- // 2. DescribeStatement (verify validity / PARSE) -- This step could be skipped.
- // 3. Execute
- assertEquals(3, requests.size());
- ExecuteSqlRequest describeParamsRequest = requests.get(0);
- assertEquals(describeSql, describeParamsRequest.getSql());
- assertEquals(QueryMode.PLAN, describeParamsRequest.getQueryMode());
- ExecuteSqlRequest describeRequest = requests.get(1);
+ // 1. DescribeStatement
+ // 2. Execute
+ assertEquals(2, requests.size());
+ ExecuteSqlRequest describeRequest = requests.get(0);
assertEquals(sql, describeRequest.getSql());
assertEquals(QueryMode.PLAN, describeRequest.getQueryMode());
- ExecuteSqlRequest executeRequest = requests.get(2);
+ ExecuteSqlRequest executeRequest = requests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
}
@@ -733,14 +686,13 @@ public void testCreateInBatches() {
@Test
public void testTransaction() {
String sql = "INSERT INTO \"all_types\" (\"col_varchar\") VALUES ($1)";
- String describeSql = "select $1 from (select \"col_varchar\"=$1 from \"all_types\") p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(sql),
ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setMetadata(createParameterTypesMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 0L));
mockSpanner.putStatementResult(
StatementResult.update(Statement.newBuilder(sql).bind("p1").to("1").build(), 1L));
mockSpanner.putStatementResult(
@@ -750,35 +702,25 @@ public void testTransaction() {
assertNull(res);
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- assertEquals(4, requests.size());
- ExecuteSqlRequest describeParamsRequest = requests.get(0);
- assertEquals(describeSql, describeParamsRequest.getSql());
- assertEquals(QueryMode.PLAN, describeParamsRequest.getQueryMode());
- assertTrue(describeParamsRequest.hasTransaction());
- assertTrue(describeParamsRequest.getTransaction().hasBegin());
- assertTrue(describeParamsRequest.getTransaction().getBegin().hasReadWrite());
-
- ExecuteSqlRequest describeRequest = requests.get(1);
+ assertEquals(3, requests.size());
+ ExecuteSqlRequest describeRequest = requests.get(0);
assertEquals(sql, describeRequest.getSql());
assertEquals(QueryMode.PLAN, describeRequest.getQueryMode());
assertTrue(describeRequest.hasTransaction());
- assertTrue(describeRequest.getTransaction().hasId());
+ assertTrue(describeRequest.getTransaction().hasBegin());
+ assertTrue(describeRequest.getTransaction().getBegin().hasReadWrite());
- ExecuteSqlRequest executeRequest1 = requests.get(2);
+ ExecuteSqlRequest executeRequest1 = requests.get(1);
assertEquals(sql, executeRequest1.getSql());
assertEquals(QueryMode.NORMAL, executeRequest1.getQueryMode());
assertTrue(executeRequest1.hasTransaction());
assertTrue(executeRequest1.getTransaction().hasId());
- assertEquals(
- describeRequest.getTransaction().getId(), executeRequest1.getTransaction().getId());
ExecuteSqlRequest executeRequest2 = requests.get(2);
assertEquals(sql, executeRequest2.getSql());
assertEquals(QueryMode.NORMAL, executeRequest2.getQueryMode());
assertTrue(executeRequest2.hasTransaction());
assertTrue(executeRequest2.getTransaction().hasId());
- assertEquals(
- describeRequest.getTransaction().getId(), executeRequest2.getTransaction().getId());
assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
CommitRequest commitRequest = mockSpanner.getRequestsOfType(CommitRequest.class).get(0);
@@ -788,14 +730,13 @@ public void testTransaction() {
@Test
public void testNestedTransaction() {
String sql = "INSERT INTO \"all_types\" (\"col_varchar\") VALUES ($1)";
- String describeSql = "select $1 from (select \"col_varchar\"=$1 from \"all_types\") p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(sql),
ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setMetadata(createParameterTypesMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 0L));
mockSpanner.putStatementResult(
StatementResult.update(Statement.newBuilder(sql).bind("p1").to("1").build(), 1L));
mockSpanner.putStatementResult(
@@ -812,28 +753,26 @@ public void testNestedTransaction() {
@Test
public void testErrorInTransaction() {
String insertSql = "INSERT INTO \"all_types\" (\"col_varchar\") VALUES ($1)";
- String describeInsertSql = "select $1 from (select \"col_varchar\"=$1 from \"all_types\") p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeInsertSql),
+ Statement.of(insertSql),
ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setMetadata(createParameterTypesMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(insertSql), 0L));
mockSpanner.putStatementResult(
StatementResult.exception(
Statement.newBuilder(insertSql).bind("p1").to("1").build(),
Status.ALREADY_EXISTS.withDescription("Row [1] already exists").asRuntimeException()));
String updateSql = "UPDATE \"all_types\" SET \"col_int\"=$1 WHERE \"col_varchar\" = $2";
- String describeUpdateSql =
- "select $1, $2 from (select \"col_int\"=$1 from \"all_types\" WHERE \"col_varchar\" = $2) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeUpdateSql),
+ Statement.of(updateSql),
ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.INT64, TypeCode.STRING)))
+ .setMetadata(
+ createParameterTypesMetadata(ImmutableList.of(TypeCode.INT64, TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(updateSql), 0L));
mockSpanner.putStatementResult(
StatementResult.update(
Statement.newBuilder(updateSql).bind("p1").to(100L).bind("p2").to("1").build(), 1L));
@@ -843,27 +782,30 @@ public void testErrorInTransaction() {
"failed to execute transaction: ERROR: current transaction is aborted, commands ignored until end of transaction block (SQLSTATE 25P02)",
res);
assertEquals(1, mockSpanner.countRequestsOfType(RollbackRequest.class));
- // This test also leads to 1 commit request. The reason for this is that the update statement is
- // also described when gorm tries to execute it. At that point, there is no read/write
- // transaction anymore on the underlying Spanner connection, as that transaction was rolled back
- // when the insert statement failed. It is therefore executed using auto-commit, which again
- // automatically leads to a commit. This is not a problem, as it is just an analyze of an update
- // statement.
- assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
+ assertEquals(0, mockSpanner.countRequestsOfType(CommitRequest.class));
}
@Test
public void testReadOnlyTransaction() {
String sql = "SELECT * FROM \"all_types\" WHERE \"all_types\".\"col_varchar\" = $1";
- String describeSql =
- "select $1 from (SELECT * FROM \"all_types\" WHERE \"all_types\".\"col_varchar\" = $1) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(sql),
ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setMetadata(
+ ALL_TYPES_METADATA
+ .toBuilder()
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.STRING).build())
+ .build())
+ .build())
+ .build())
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.query(Statement.of(sql), ALL_TYPES_RESULTSET));
mockSpanner.putStatementResult(
StatementResult.query(
Statement.newBuilder(sql).bind("p1").to("1").build(), ALL_TYPES_RESULTSET));
@@ -880,24 +822,17 @@ public void testReadOnlyTransaction() {
assertTrue(beginRequest.getOptions().hasReadOnly());
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- assertEquals(4, requests.size());
+ assertEquals(3, requests.size());
ExecuteSqlRequest describeRequest = requests.get(0);
assertEquals(sql, describeRequest.getSql());
assertEquals(QueryMode.PLAN, describeRequest.getQueryMode());
assertTrue(describeRequest.getTransaction().hasId());
- ExecuteSqlRequest describeParamsRequest = requests.get(1);
- assertEquals(describeSql, describeParamsRequest.getSql());
- assertEquals(QueryMode.PLAN, describeParamsRequest.getQueryMode());
- assertEquals(
- describeParamsRequest.getTransaction().getId(), describeRequest.getTransaction().getId());
-
- ExecuteSqlRequest executeRequest = requests.get(2);
+ ExecuteSqlRequest executeRequest = requests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
- assertEquals(
- describeParamsRequest.getTransaction().getId(), executeRequest.getTransaction().getId());
+ assertEquals(describeRequest.getTransaction().getId(), executeRequest.getTransaction().getId());
// The read-only transaction is 'committed', but that does not cause a CommitRequest to be sent
// to Cloud Spanner.
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxMockServerTest.java
index 236fdfc7b3..bb3c157aff 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxMockServerTest.java
@@ -45,6 +45,7 @@
import com.google.spanner.v1.Mutation.OperationCase;
import com.google.spanner.v1.ResultSet;
import com.google.spanner.v1.ResultSetMetadata;
+import com.google.spanner.v1.ResultSetStats;
import com.google.spanner.v1.RollbackRequest;
import com.google.spanner.v1.StructType;
import com.google.spanner.v1.StructType.Field;
@@ -56,7 +57,6 @@
import java.util.List;
import java.util.stream.Collectors;
import org.junit.BeforeClass;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@@ -76,7 +76,7 @@
public class PgxMockServerTest extends AbstractMockServerTest {
private static PgxTest pgxTest;
- @Rule public Timeout globalTimeout = Timeout.seconds(30L);
+ @Rule public Timeout globalTimeout = Timeout.seconds(300L);
@Parameter public boolean useDomainSocket;
@@ -176,19 +176,23 @@ public void testQueryWithParameter() {
.build();
// Add a query result for the statement parameter types.
- String selectParamsSql = "select $1 from (SELECT * FROM FOO WHERE BAR=$1) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(selectParamsSql),
+ Statement.of(sql),
ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setMetadata(
+ metadata
+ .toBuilder()
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.STRING).build())
+ .build())
+ .build()))
.build()));
-
- // Add a query result with only the metadata for the query without parameter values.
- mockSpanner.putStatementResult(
- StatementResult.query(
- Statement.of(sql), ResultSet.newBuilder().setMetadata(metadata).build()));
- // Also add a query result with both metadata and rows for the statement with parameter values.
+ // Add a query result with both metadata and rows for the statement with parameter values.
mockSpanner.putStatementResult(
StatementResult.query(
statement,
@@ -204,19 +208,15 @@ public void testQueryWithParameter() {
assertNull(res);
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- // pgx by default always uses prepared statements. That means that each request is sent three
+ // pgx by default always uses prepared statements. That means that each request is sent two
// times to the backend:
- // 1. DescribeStatement (results)
- // 2. DescribeStatement (parameters)
- // 3. Execute (including DescribePortal)
- assertEquals(3, requests.size());
+ // 1. DescribeStatement (parameters + results)
+ // 2. Execute (including DescribePortal)
+ assertEquals(2, requests.size());
ExecuteSqlRequest describeStatementRequest = requests.get(0);
assertEquals(sql, describeStatementRequest.getSql());
assertEquals(QueryMode.PLAN, describeStatementRequest.getQueryMode());
- ExecuteSqlRequest describeParametersRequest = requests.get(1);
- assertEquals(selectParamsSql, describeParametersRequest.getSql());
- assertEquals(QueryMode.PLAN, describeParametersRequest.getQueryMode());
- ExecuteSqlRequest executeRequest = requests.get(2);
+ ExecuteSqlRequest executeRequest = requests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
}
@@ -268,15 +268,12 @@ public void testQueryAllDataTypes() {
public void testUpdateAllDataTypes() {
String sql =
"UPDATE \"all_types\" SET \"col_bigint\"=$1,\"col_bool\"=$2,\"col_bytea\"=$3,\"col_float8\"=$4,\"col_int\"=$5,\"col_numeric\"=$6,\"col_timestamptz\"=$7,\"col_date\"=$8,\"col_varchar\"=$9,\"col_jsonb\"=$10 WHERE \"col_varchar\" = $11";
- String describeSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11 from "
- + "(select \"col_bigint\"=$1, \"col_bool\"=$2, \"col_bytea\"=$3, \"col_float8\"=$4, \"col_int\"=$5, \"col_numeric\"=$6, \"col_timestamptz\"=$7, \"col_date\"=$8, \"col_varchar\"=$9, \"col_jsonb\"=$10 from \"all_types\" WHERE \"col_varchar\" = $11) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(sql),
ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.BOOL,
@@ -289,8 +286,8 @@ public void testUpdateAllDataTypes() {
TypeCode.STRING,
TypeCode.STRING,
TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 0L));
mockSpanner.putStatementResult(
StatementResult.update(
Statement.newBuilder(sql)
@@ -323,19 +320,15 @@ public void testUpdateAllDataTypes() {
assertNull(res);
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- // pgx by default always uses prepared statements. That means that each request is sent three
+ // pgx by default always uses prepared statements. That means that each request is sent two
// times to the backend the first time it is executed:
// 1. DescribeStatement (parameters)
- // 2. DescribeStatement (verify validity / PARSE) -- This step could be skipped.
- // 3. Execute
- assertEquals(3, requests.size());
+ // 2. Execute
+ assertEquals(2, requests.size());
ExecuteSqlRequest describeParamsRequest = requests.get(0);
- assertEquals(describeSql, describeParamsRequest.getSql());
+ assertEquals(sql, describeParamsRequest.getSql());
assertEquals(QueryMode.PLAN, describeParamsRequest.getQueryMode());
- ExecuteSqlRequest describeRequest = requests.get(1);
- assertEquals(sql, describeRequest.getSql());
- assertEquals(QueryMode.PLAN, describeRequest.getQueryMode());
- ExecuteSqlRequest executeRequest = requests.get(2);
+ ExecuteSqlRequest executeRequest = requests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
}
@@ -346,14 +339,12 @@ public void testInsertAllDataTypes() {
"INSERT INTO all_types "
+ "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar, col_jsonb) "
+ "values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
- String describeSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 from (select col_bigint=$1, col_bool=$2, col_bytea=$3, col_float8=$4, col_int=$5, col_numeric=$6, col_timestamptz=$7, col_date=$8, col_varchar=$9, col_jsonb=$10 from all_types) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(sql),
ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.BOOL,
@@ -365,8 +356,8 @@ public void testInsertAllDataTypes() {
TypeCode.DATE,
TypeCode.STRING,
TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 0L));
mockSpanner.putStatementResult(
StatementResult.update(
Statement.newBuilder(sql)
@@ -397,19 +388,15 @@ public void testInsertAllDataTypes() {
assertNull(res);
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- // pgx by default always uses prepared statements. That means that each request is sent three
+ // pgx by default always uses prepared statements. That means that each request is sent two
// times to the backend the first time it is executed:
// 1. DescribeStatement (parameters)
- // 2. DescribeStatement (verify validity / PARSE) -- This step could be skipped.
- // 3. Execute
- assertEquals(3, requests.size());
+ // 2. Execute
+ assertEquals(2, requests.size());
ExecuteSqlRequest describeParamsRequest = requests.get(0);
- assertEquals(describeSql, describeParamsRequest.getSql());
+ assertEquals(sql, describeParamsRequest.getSql());
assertEquals(QueryMode.PLAN, describeParamsRequest.getQueryMode());
- ExecuteSqlRequest describeRequest = requests.get(1);
- assertEquals(sql, describeRequest.getSql());
- assertEquals(QueryMode.PLAN, describeRequest.getQueryMode());
- ExecuteSqlRequest executeRequest = requests.get(2);
+ ExecuteSqlRequest executeRequest = requests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
}
@@ -420,14 +407,12 @@ public void testInsertBatch() {
"INSERT INTO all_types "
+ "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar, col_jsonb) "
+ "values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
- String describeSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 from (select col_bigint=$1, col_bool=$2, col_bytea=$3, col_float8=$4, col_int=$5, col_numeric=$6, col_timestamptz=$7, col_date=$8, col_varchar=$9, col_jsonb=$10 from all_types) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(sql),
ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.BOOL,
@@ -439,8 +424,8 @@ public void testInsertBatch() {
TypeCode.DATE,
TypeCode.STRING,
TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 0L));
int batchSize = 10;
for (int i = 0; i < batchSize; i++) {
mockSpanner.putStatementResult(
@@ -476,24 +461,17 @@ public void testInsertBatch() {
assertNull(res);
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- // pgx by default always uses prepared statements. That means that each request is sent 2 times
- // to the backend to be described, and then `batchSize` times to be executed.
+ // pgx by default always uses prepared statements. That means that each request is sent once
+ // to the backend to be described, and then `batchSize` times to be executed (which is sent as
+ // one BatchDML request).
// 1. DescribeStatement (parameters)
- // 2. DescribeStatement (verify validity / PARSE) -- This step could be skipped.
- // 3. Execute 10 times.
- assertEquals(2, requests.size());
+ // 2. Execute 10 times.
+ assertEquals(1, requests.size());
ExecuteSqlRequest describeParamsRequest = requests.get(0);
- assertEquals(describeSql, describeParamsRequest.getSql());
+ assertEquals(sql, describeParamsRequest.getSql());
assertEquals(QueryMode.PLAN, describeParamsRequest.getQueryMode());
- // The 'describe' query for the parameters will be executed as a single use transaction.
- assertTrue(describeParamsRequest.getTransaction().hasSingleUse());
-
- // The analyzeUpdate that is executed to verify the validity of the DML statement is executed as
- // a separate transaction.
- ExecuteSqlRequest describeRequest = requests.get(1);
- assertEquals(sql, describeRequest.getSql());
- assertEquals(QueryMode.PLAN, describeRequest.getQueryMode());
- assertTrue(describeRequest.getTransaction().hasBegin());
+ assertTrue(describeParamsRequest.getTransaction().hasBegin());
+ assertTrue(describeParamsRequest.getTransaction().getBegin().hasReadWrite());
assertEquals(1, mockSpanner.countRequestsOfType(ExecuteBatchDmlRequest.class));
ExecuteBatchDmlRequest batchDmlRequest =
@@ -501,10 +479,10 @@ public void testInsertBatch() {
assertEquals(batchSize, batchDmlRequest.getStatementsCount());
assertTrue(batchDmlRequest.getTransaction().hasBegin());
- // There are two commit requests, as the 'Describe statement' message is executed as a separate
- // transaction.
- List commitRequests = mockSpanner.getRequestsOfType(CommitRequest.class);
- assertEquals(2, commitRequests.size());
+ // There are two commit requests:
+ // 1. One for the analyzeUpdate to describe the insert statement.
+ // 2. One for the actual batch.
+ assertEquals(2, mockSpanner.countRequestsOfType(CommitRequest.class));
}
@Test
@@ -513,14 +491,12 @@ public void testMixedBatch() {
"INSERT INTO all_types "
+ "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar, col_jsonb) "
+ "values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
- String describeInsertSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 from (select col_bigint=$1, col_bool=$2, col_bytea=$3, col_float8=$4, col_int=$5, col_numeric=$6, col_timestamptz=$7, col_date=$8, col_varchar=$9, col_jsonb=$10 from all_types) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeInsertSql),
+ Statement.of(insertSql),
ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.BOOL,
@@ -532,54 +508,60 @@ public void testMixedBatch() {
TypeCode.DATE,
TypeCode.STRING,
TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(insertSql), 0L));
String selectSql = "select count(*) from all_types where col_bool=$1";
- ResultSet resultSet =
- ResultSet.newBuilder()
- .setMetadata(
- ResultSetMetadata.newBuilder()
- .setRowType(
- StructType.newBuilder()
- .addFields(
- Field.newBuilder()
- .setName("c")
- .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
- .build())
+ ResultSetMetadata metadata =
+ ResultSetMetadata.newBuilder()
+ .setRowType(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("c")
+ .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
.build())
.build())
+ .build();
+ ResultSet resultSet =
+ ResultSet.newBuilder()
+ .setMetadata(metadata)
.addRows(
ListValue.newBuilder()
.addValues(Value.newBuilder().setStringValue("3").build())
.build())
.build();
- mockSpanner.putStatementResult(StatementResult.query(Statement.of(selectSql), resultSet));
- mockSpanner.putStatementResult(
- StatementResult.query(
- Statement.newBuilder(selectSql).bind("p1").to(true).build(), resultSet));
-
- String describeParamsSelectSql =
- "select $1 from (select count(*) from all_types where col_bool=$1) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeParamsSelectSql),
+ Statement.of(selectSql),
ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.BOOL)))
+ .setMetadata(
+ metadata
+ .toBuilder()
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.BOOL).build())
+ .build())
+ .build())
+ .build())
.build()));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.newBuilder(selectSql).bind("p1").to(true).build(), resultSet));
String updateSql = "update all_types set col_bool=false where col_bool=$1";
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(updateSql), 0L));
- mockSpanner.putStatementResult(
- StatementResult.update(Statement.newBuilder(updateSql).bind("p1").to(true).build(), 3L));
- String describeUpdateSql =
- "select $1 from (select col_bool=false from all_types where col_bool=$1) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeUpdateSql),
+ Statement.of(updateSql),
ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.BOOL)))
+ .setMetadata(createParameterTypesMetadata(ImmutableList.of(TypeCode.BOOL)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
+ mockSpanner.putStatementResult(
+ StatementResult.update(Statement.newBuilder(updateSql).bind("p1").to(true).build(), 3L));
int batchSize = 5;
for (int i = 0; i < batchSize; i++) {
@@ -616,91 +598,62 @@ public void testMixedBatch() {
assertNull(res);
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- // pgx by default always uses prepared statements. That means that we get the following list of
- // statements:
+ // pgx by default always uses prepared statements. In addition, pgx first describes all
+ // statements in a batch before it executes the batch. The describe-messages that it sends do
+ // not use the current transaction.
+ // That means that we get the following list of statements:
// 1. Describe parameters of insert statement in PLAN mode.
- // 2. Parse insert statement in PLAN mode.
- // 3. Describe columns of select statement in PLAN mode.
- // 4. Describe parameters of select statement in PLAN mode.
- // 5. Describe parameters of update statement in PLAN mode.
- // 6. Parse update statement in PLAN mode.
- // 7. Execute select statement.
- // 8. Execute update statement.
- assertEquals(8, requests.size());
+ // 2. Describe parameters and columns of select statement in PLAN mode.
+ // 3. Describe parameters of update statement in PLAN mode.
+ // 4. Execute select statement.
+ // 5. Execute update statement.
+ // In addition, we get one ExecuteBatchDml request for the insert statements.
+ assertEquals(5, requests.size());
// NOTE: pgx will first create prepared statements for sql strings that it does not yet know.
// All those describe statement messages will be executed in separate (single-use) transactions.
// The order in which the describe statements are executed is random.
- ExecuteSqlRequest describeInsertParamsRequest =
- requests.stream()
- .filter(request -> request.getSql().equals(describeInsertSql))
- .findFirst()
- .orElse(ExecuteSqlRequest.getDefaultInstance());
- assertEquals(describeInsertSql, describeInsertParamsRequest.getSql());
- assertEquals(QueryMode.PLAN, describeInsertParamsRequest.getQueryMode());
- // The 'describe' query for the parameters will be executed as a single use transaction.
- assertTrue(describeInsertParamsRequest.getTransaction().hasSingleUse());
-
- ExecuteSqlRequest parseInsertRequest =
+ List describeInsertRequests =
requests.stream()
.filter(
request ->
- request.getSql().equals(insertSql) && request.getQueryMode() == QueryMode.PLAN)
- .findFirst()
- .orElse(ExecuteSqlRequest.getDefaultInstance());
- assertEquals(insertSql, parseInsertRequest.getSql());
- assertEquals(QueryMode.PLAN, parseInsertRequest.getQueryMode());
- assertTrue(parseInsertRequest.getTransaction().hasBegin());
-
- ExecuteSqlRequest describeSelectColumnsRequest =
+ request.getSql().equals(insertSql)
+ && request.getQueryMode().equals(QueryMode.PLAN))
+ .collect(Collectors.toList());
+ assertEquals(1, describeInsertRequests.size());
+ // TODO(#477): These Describe-message flows could use single-use read/write transactions.
+ assertTrue(describeInsertRequests.get(0).getTransaction().hasBegin());
+ assertTrue(describeInsertRequests.get(0).getTransaction().getBegin().hasReadWrite());
+
+ List describeSelectRequests =
requests.stream()
.filter(
request ->
request.getSql().equals(selectSql) && request.getQueryMode() == QueryMode.PLAN)
- .findFirst()
- .orElse(ExecuteSqlRequest.getDefaultInstance());
- assertEquals(selectSql, describeSelectColumnsRequest.getSql());
- assertEquals(QueryMode.PLAN, describeSelectColumnsRequest.getQueryMode());
- assertTrue(describeSelectColumnsRequest.getTransaction().hasSingleUse());
+ .collect(Collectors.toList());
+ assertEquals(1, describeSelectRequests.size());
+ assertTrue(describeSelectRequests.get(0).getTransaction().hasSingleUse());
+ assertTrue(describeSelectRequests.get(0).getTransaction().getSingleUse().hasReadOnly());
- ExecuteSqlRequest describeSelectParamsRequest =
- requests.stream()
- .filter(request -> request.getSql().equals(describeParamsSelectSql))
- .findFirst()
- .orElse(ExecuteSqlRequest.getDefaultInstance());
- assertEquals(describeParamsSelectSql, describeSelectParamsRequest.getSql());
- assertEquals(QueryMode.PLAN, describeSelectParamsRequest.getQueryMode());
- assertTrue(describeSelectParamsRequest.getTransaction().hasSingleUse());
-
- ExecuteSqlRequest describeUpdateParamsRequest =
- requests.stream()
- .filter(request -> request.getSql().equals(describeUpdateSql))
- .findFirst()
- .orElse(ExecuteSqlRequest.getDefaultInstance());
- assertEquals(describeUpdateSql, describeUpdateParamsRequest.getSql());
- assertEquals(QueryMode.PLAN, describeUpdateParamsRequest.getQueryMode());
- assertTrue(describeUpdateParamsRequest.getTransaction().hasSingleUse());
-
- ExecuteSqlRequest parseUpdateRequest =
+ List describeUpdateRequests =
requests.stream()
.filter(
request ->
request.getSql().equals(updateSql) && request.getQueryMode() == QueryMode.PLAN)
- .findFirst()
- .orElse(ExecuteSqlRequest.getDefaultInstance());
- assertEquals(updateSql, parseUpdateRequest.getSql());
- assertEquals(QueryMode.PLAN, parseUpdateRequest.getQueryMode());
- assertTrue(parseUpdateRequest.getTransaction().hasBegin());
+ .collect(Collectors.toList());
+ assertEquals(1, describeUpdateRequests.size());
+ assertTrue(describeUpdateRequests.get(0).getTransaction().hasBegin());
+ assertTrue(describeUpdateRequests.get(0).getTransaction().getBegin().hasReadWrite());
// From here we start with the actual statement execution.
- ExecuteSqlRequest executeSelectRequest = requests.get(6);
+ ExecuteSqlRequest executeSelectRequest = requests.get(3);
assertEquals(selectSql, executeSelectRequest.getSql());
assertEquals(QueryMode.NORMAL, executeSelectRequest.getQueryMode());
// The SELECT statement should use the transaction that was started by the BatchDml request.
assertTrue(executeSelectRequest.getTransaction().hasId());
- ExecuteSqlRequest executeUpdateRequest = requests.get(7);
+ ExecuteSqlRequest executeUpdateRequest = requests.get(4);
assertEquals(updateSql, executeUpdateRequest.getSql());
assertEquals(QueryMode.NORMAL, executeUpdateRequest.getQueryMode());
assertTrue(executeUpdateRequest.getTransaction().hasId());
@@ -710,6 +663,9 @@ public void testMixedBatch() {
mockSpanner.getRequestsOfType(ExecuteBatchDmlRequest.class).get(0);
assertEquals(batchSize, batchDmlRequest.getStatementsCount());
assertTrue(batchDmlRequest.getTransaction().hasBegin());
+ for (int i = 0; i < batchSize; i++) {
+ assertEquals(insertSql, batchDmlRequest.getStatements(i).getSql());
+ }
// There are three commit requests:
// 1. Describe insert statement.
@@ -728,48 +684,45 @@ public void testMixedBatch() {
|| request instanceof ExecuteBatchDmlRequest
|| request instanceof CommitRequest)
.collect(Collectors.toList());
- // 12 == 3 Commit + 1 Batch DML + 8 ExecuteSql.
- assertEquals(12, allRequests.size());
+ // 9 == 3 Commit + 1 Batch DML + 5 ExecuteSql.
+ assertEquals(9, allRequests.size());
// We don't know the exact order of the DESCRIBE requests.
// The order of EXECUTE requests is known and fixed.
// The (theoretical) order of DESCRIBE requests is:
// 1. Describe parameters of insert statement in PLAN mode.
- // 2. Parse insert statement in PLAN mode.
- // 3. Commit.
- // 4. Describe columns of select statement in PLAN mode.
- // 5. Describe parameters of select statement in PLAN mode.
- // 6. Describe parameters of update statement in PLAN mode.
- // 7. Parse update statement in PLAN mode.
- // 8. Commit.
+ // 2. Commit.
+ // 3. Describe parameters and columns of select statement in PLAN mode.
+ // 4. Describe parameters of update statement in PLAN mode.
+ // 5. Commit.
// The fixed order of EXECUTE requests is:
- // 9. Execute insert batch (ExecuteBatchDml).
- // 10. Execute select statement.
- // 11. Execute update statement.
- // 12. Commit transaction.
+ // 6. Execute insert batch (ExecuteBatchDml).
+ // 7. Execute select statement.
+ // 8. Execute update statement.
+ // 9. Commit transaction.
assertEquals(
2,
- allRequests.subList(0, 8).stream()
+ allRequests.subList(0, 5).stream()
.filter(request -> request instanceof CommitRequest)
.count());
assertEquals(
- 6,
- allRequests.subList(0, 8).stream()
+ 3,
+ allRequests.subList(0, 5).stream()
.filter(
request ->
request instanceof ExecuteSqlRequest
&& ((ExecuteSqlRequest) request).getQueryMode() == QueryMode.PLAN)
.count());
- assertEquals(ExecuteBatchDmlRequest.class, allRequests.get(8).getClass());
- assertEquals(ExecuteSqlRequest.class, allRequests.get(9).getClass());
- assertEquals(ExecuteSqlRequest.class, allRequests.get(10).getClass());
- assertEquals(CommitRequest.class, allRequests.get(11).getClass());
-
- ByteString transactionId = ((CommitRequest) allRequests.get(11)).getTransactionId();
- assertEquals(transactionId, ((ExecuteSqlRequest) allRequests.get(9)).getTransaction().getId());
- assertEquals(transactionId, ((ExecuteSqlRequest) allRequests.get(10)).getTransaction().getId());
+ assertEquals(ExecuteBatchDmlRequest.class, allRequests.get(5).getClass());
+ assertEquals(ExecuteSqlRequest.class, allRequests.get(6).getClass());
+ assertEquals(ExecuteSqlRequest.class, allRequests.get(7).getClass());
+ assertEquals(CommitRequest.class, allRequests.get(8).getClass());
+
+ ByteString transactionId = ((CommitRequest) allRequests.get(8)).getTransactionId();
+ assertEquals(transactionId, ((ExecuteSqlRequest) allRequests.get(6)).getTransaction().getId());
+ assertEquals(transactionId, ((ExecuteSqlRequest) allRequests.get(7)).getTransaction().getId());
}
@Test
@@ -778,15 +731,12 @@ public void testBatchPrepareError() {
"INSERT INTO all_types "
+ "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar, col_jsonb) "
+ "values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(insertSql), 0L));
- String describeInsertSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 from (select col_bigint=$1, col_bool=$2, col_bytea=$3, col_float8=$4, col_int=$5, col_numeric=$6, col_timestamptz=$7, col_date=$8, col_varchar=$9, col_jsonb=$10 from all_types) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeInsertSql),
+ Statement.of(insertSql),
ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.BOOL,
@@ -798,6 +748,7 @@ public void testBatchPrepareError() {
TypeCode.DATE,
TypeCode.STRING,
TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
// This select statement will fail during the PREPARE phase that pgx executes for all statements
// before actually executing the batch.
@@ -840,14 +791,12 @@ public void testBatchExecutionError() {
"INSERT INTO all_types "
+ "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar, col_jsonb) "
+ "values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
- String describeInsertSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 from (select col_bigint=$1, col_bool=$2, col_bytea=$3, col_float8=$4, col_int=$5, col_numeric=$6, col_timestamptz=$7, col_date=$8, col_varchar=$9, col_jsonb=$10 from all_types) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeInsertSql),
+ Statement.of(insertSql),
ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.BOOL,
@@ -859,8 +808,8 @@ public void testBatchExecutionError() {
TypeCode.DATE,
TypeCode.STRING,
TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(insertSql), 0L));
int batchSize = 3;
for (int i = 0; i < batchSize; i++) {
Statement statement =
@@ -920,14 +869,12 @@ public void testInsertNullsAllDataTypes() {
"INSERT INTO all_types "
+ "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar, col_jsonb) "
+ "values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
- String describeSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 from (select col_bigint=$1, col_bool=$2, col_bytea=$3, col_float8=$4, col_int=$5, col_numeric=$6, col_timestamptz=$7, col_date=$8, col_varchar=$9, col_jsonb=$10 from all_types) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(sql),
ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.BOOL,
@@ -939,8 +886,8 @@ public void testInsertNullsAllDataTypes() {
TypeCode.DATE,
TypeCode.STRING,
TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 0L));
mockSpanner.putStatementResult(
StatementResult.update(
Statement.newBuilder(sql)
@@ -971,19 +918,15 @@ public void testInsertNullsAllDataTypes() {
assertNull(res);
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- // pgx by default always uses prepared statements. That means that each request is sent three
+ // pgx by default always uses prepared statements. That means that each request is sent two
// times to the backend the first time it is executed:
// 1. DescribeStatement (parameters)
- // 2. DescribeStatement (verify validity / PARSE) -- This step could be skipped.
- // 3. Execute
- assertEquals(3, requests.size());
- ExecuteSqlRequest describeParamsRequest = requests.get(0);
- assertEquals(describeSql, describeParamsRequest.getSql());
- assertEquals(QueryMode.PLAN, describeParamsRequest.getQueryMode());
- ExecuteSqlRequest describeRequest = requests.get(1);
+ // 2. Execute
+ assertEquals(2, requests.size());
+ ExecuteSqlRequest describeRequest = requests.get(0);
assertEquals(sql, describeRequest.getSql());
assertEquals(QueryMode.PLAN, describeRequest.getQueryMode());
- ExecuteSqlRequest executeRequest = requests.get(2);
+ ExecuteSqlRequest executeRequest = requests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
}
@@ -1073,14 +1016,12 @@ public void testReadWriteTransaction() {
"INSERT INTO all_types "
+ "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar, col_jsonb) "
+ "values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
- String describeSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 from (select col_bigint=$1, col_bool=$2, col_bytea=$3, col_float8=$4, col_int=$5, col_numeric=$6, col_timestamptz=$7, col_date=$8, col_varchar=$9, col_jsonb=$10 from all_types) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeSql),
+ Statement.of(sql),
ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.BOOL,
@@ -1092,8 +1033,8 @@ public void testReadWriteTransaction() {
TypeCode.DATE,
TypeCode.STRING,
TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 0L));
for (long id : new Long[] {10L, 20L}) {
mockSpanner.putStatementResult(
StatementResult.update(
@@ -1127,14 +1068,12 @@ public void testReadWriteTransaction() {
assertNull(res);
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
// pgx by default always uses prepared statements. That means that the first time a SQL
- // statement is executed, it will be sent three times to the backend (twice for statements
- // without any query parameters):
- // 1. DescribeStatement (parameters)
- // 2. DescribeStatement (verify validity / PARSE) -- This step could be skipped.
- // 3. Execute
+ // statement is executed, it will be sent two times to the backend:
+ // 1. DescribeStatement
+ // 2. Execute
// The second time the same statement is executed, it is only sent once.
- assertEquals(6, requests.size());
+ assertEquals(5, requests.size());
ExecuteSqlRequest describeSelect1Request = requests.get(0);
// The first statement should begin the transaction.
assertTrue(describeSelect1Request.getTransaction().hasBegin());
@@ -1144,21 +1083,17 @@ public void testReadWriteTransaction() {
assertTrue(executeSelect1Request.getTransaction().hasId());
assertEquals(QueryMode.NORMAL, executeSelect1Request.getQueryMode());
- ExecuteSqlRequest describeParamsRequest = requests.get(2);
- assertEquals(describeSql, describeParamsRequest.getSql());
- assertEquals(QueryMode.PLAN, describeParamsRequest.getQueryMode());
- assertTrue(describeParamsRequest.getTransaction().hasId());
-
- ExecuteSqlRequest describeRequest = requests.get(3);
+ ExecuteSqlRequest describeRequest = requests.get(2);
assertEquals(sql, describeRequest.getSql());
assertEquals(QueryMode.PLAN, describeRequest.getQueryMode());
assertTrue(describeRequest.getTransaction().hasId());
- ExecuteSqlRequest executeRequest = requests.get(4);
- assertEquals(sql, executeRequest.getSql());
- assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
- assertTrue(executeRequest.getTransaction().hasId());
- assertTrue(requests.get(3).getTransaction().hasId());
+ for (int i = 3; i < 5; i++) {
+ ExecuteSqlRequest executeRequest = requests.get(i);
+ assertEquals(sql, executeRequest.getSql());
+ assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
+ assertTrue(executeRequest.getTransaction().hasId());
+ }
assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
CommitRequest commitRequest = mockSpanner.getRequestsOfType(CommitRequest.class).get(0);
@@ -1222,7 +1157,6 @@ public void testReadWriteTransactionIsolationLevelRepeatableRead() {
assertEquals(0, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
}
- @Ignore("Requires Spanner client library 6.26.0")
@Test
public void testReadOnlySerializableTransaction() {
String res = pgxTest.TestReadOnlySerializableTransaction(createConnString());
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxSimpleModeMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxSimpleModeMockServerTest.java
index 17a11a6117..2012da7c5b 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxSimpleModeMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxSimpleModeMockServerTest.java
@@ -37,7 +37,6 @@
import java.nio.charset.StandardCharsets;
import java.util.List;
import org.junit.BeforeClass;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
@@ -209,7 +208,6 @@ public void testInsertNullsAllDataTypes() {
}
@Test
- @Ignore("Skip until https://github.com/googleapis/java-spanner/pull/1877 has been released")
public void testWrongDialect() {
// Let the mock server respond with the Google SQL dialect instead of PostgreSQL. The
// connection should be gracefully rejected. Close all open pooled Spanner objects so we know
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/metadata/DescribeResultTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/metadata/DescribeResultTest.java
new file mode 100644
index 0000000000..24b7079522
--- /dev/null
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/metadata/DescribeResultTest.java
@@ -0,0 +1,113 @@
+// Copyright 2022 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.cloud.spanner.pgadapter.metadata;
+
+import static com.google.cloud.spanner.pgadapter.metadata.DescribeResult.extractParameterTypes;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import com.google.cloud.spanner.pgadapter.error.PGException;
+import com.google.spanner.v1.StructType;
+import com.google.spanner.v1.StructType.Field;
+import com.google.spanner.v1.Type;
+import com.google.spanner.v1.TypeCode;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.postgresql.core.Oid;
+
+@RunWith(JUnit4.class)
+public class DescribeResultTest {
+
+ @Test
+ public void testExtractParameterTypes() {
+ assertArrayEquals(
+ new int[] {}, extractParameterTypes(new int[] {}, StructType.newBuilder().build()));
+ assertArrayEquals(
+ new int[] {Oid.INT8},
+ extractParameterTypes(
+ new int[] {},
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
+ .build())
+ .build()));
+ assertArrayEquals(
+ new int[] {Oid.BOOL},
+ extractParameterTypes(
+ new int[] {},
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.BOOL).build())
+ .build())
+ .build()));
+ assertArrayEquals(
+ new int[] {Oid.INT8},
+ extractParameterTypes(
+ new int[] {Oid.INT8},
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.STRING).build())
+ .build())
+ .build()));
+ assertArrayEquals(
+ new int[] {Oid.INT8},
+ extractParameterTypes(new int[] {Oid.INT8}, StructType.newBuilder().build()));
+ assertArrayEquals(
+ new int[] {Oid.INT8, Oid.VARCHAR},
+ extractParameterTypes(
+ new int[] {Oid.INT8},
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p2")
+ .setType(Type.newBuilder().setCode(TypeCode.STRING).build())
+ .build())
+ .build()));
+ assertArrayEquals(
+ new int[] {Oid.INT8, Oid.UNSPECIFIED, Oid.VARCHAR},
+ extractParameterTypes(
+ new int[] {Oid.INT8},
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p3")
+ .setType(Type.newBuilder().setCode(TypeCode.STRING).build())
+ .build())
+ .build()));
+
+ PGException exception =
+ assertThrows(
+ PGException.class,
+ () ->
+ extractParameterTypes(
+ new int[] {},
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("foo")
+ .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
+ .build())
+ .build()));
+ assertEquals("Invalid parameter name: foo", exception.getMessage());
+ }
+}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/nodejs/NodePostgresMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/nodejs/NodePostgresMockServerTest.java
index 501ad1b287..04014463be 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/nodejs/NodePostgresMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/nodejs/NodePostgresMockServerTest.java
@@ -15,7 +15,6 @@
package com.google.cloud.spanner.pgadapter.nodejs;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.google.cloud.ByteArray;
@@ -36,6 +35,8 @@
import com.google.spanner.v1.CommitRequest;
import com.google.spanner.v1.ExecuteBatchDmlRequest;
import com.google.spanner.v1.ExecuteSqlRequest;
+import com.google.spanner.v1.ResultSet;
+import com.google.spanner.v1.ResultSetStats;
import com.google.spanner.v1.RollbackRequest;
import com.google.spanner.v1.TypeCode;
import io.grpc.Status;
@@ -100,12 +101,12 @@ public void testInsert() throws Exception {
// The result of the describe statement call is cached for that connection, so executing the
// same statement once more will not cause another describe-statement round-trip.
String sql = "INSERT INTO users(name) VALUES($1)";
- String describeParamsSql = "select $1 from (select name=$1 from users) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeParamsSql),
- com.google.spanner.v1.ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.STRING)))
+ Statement.of(sql),
+ ResultSet.newBuilder()
+ .setMetadata(createParameterTypesMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
mockSpanner.putStatementResult(
StatementResult.update(Statement.newBuilder(sql).bind("p1").to("foo").build(), 1L));
@@ -116,31 +117,31 @@ public void testInsert() throws Exception {
List executeSqlRequests =
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
- .filter(
- request ->
- request.getSql().equals(sql) || request.getSql().equals(describeParamsSql))
+ .filter(request -> request.getSql().equals(sql))
.collect(Collectors.toList());
assertEquals(2, executeSqlRequests.size());
ExecuteSqlRequest describeRequest = executeSqlRequests.get(0);
- assertEquals(describeParamsSql, describeRequest.getSql());
- assertFalse(describeRequest.hasTransaction());
+ assertEquals(sql, describeRequest.getSql());
+ assertTrue(describeRequest.hasTransaction());
+ assertTrue(describeRequest.getTransaction().hasBegin());
+ assertTrue(describeRequest.getTransaction().getBegin().hasReadWrite());
+
ExecuteSqlRequest executeRequest = executeSqlRequests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(1, executeRequest.getParamTypesCount());
- assertTrue(executeRequest.getTransaction().hasBegin());
- assertTrue(executeRequest.getTransaction().getBegin().hasReadWrite());
+ assertTrue(executeRequest.getTransaction().hasId());
assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
}
@Test
public void testInsertExecutedTwice() throws Exception {
String sql = "INSERT INTO users(name) VALUES($1)";
- String describeParamsSql = "select $1 from (select name=$1 from users) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeParamsSql),
- com.google.spanner.v1.ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.STRING)))
+ Statement.of(sql),
+ ResultSet.newBuilder()
+ .setMetadata(createParameterTypesMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
mockSpanner.putStatementResult(
StatementResult.update(Statement.newBuilder(sql).bind("p1").to("foo").build(), 1L));
@@ -153,20 +154,19 @@ public void testInsertExecutedTwice() throws Exception {
List executeSqlRequests =
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
- .filter(
- request ->
- request.getSql().equals(sql) || request.getSql().equals(describeParamsSql))
+ .filter(request -> request.getSql().equals(sql))
.collect(Collectors.toList());
assertEquals(3, executeSqlRequests.size());
ExecuteSqlRequest describeRequest = executeSqlRequests.get(0);
- assertEquals(describeParamsSql, describeRequest.getSql());
- assertFalse(describeRequest.hasTransaction());
+ assertEquals(sql, describeRequest.getSql());
+ assertTrue(describeRequest.hasTransaction());
+ assertTrue(describeRequest.getTransaction().hasBegin());
+ assertTrue(describeRequest.getTransaction().getBegin().hasReadWrite());
ExecuteSqlRequest executeRequest = executeSqlRequests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(1, executeRequest.getParamTypesCount());
- assertTrue(executeRequest.getTransaction().hasBegin());
- assertTrue(executeRequest.getTransaction().getBegin().hasReadWrite());
+ assertTrue(executeRequest.getTransaction().hasId());
executeRequest = executeSqlRequests.get(2);
assertEquals(sql, executeRequest.getSql());
@@ -178,12 +178,12 @@ public void testInsertExecutedTwice() throws Exception {
@Test
public void testInsertAutoCommit() throws IOException, InterruptedException {
String sql = "INSERT INTO users(name) VALUES($1)";
- String describeParamsSql = "select $1 from (select name=$1 from users) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeParamsSql),
- com.google.spanner.v1.ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.STRING)))
+ Statement.of(sql),
+ ResultSet.newBuilder()
+ .setMetadata(createParameterTypesMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
mockSpanner.putStatementResult(
StatementResult.update(Statement.newBuilder(sql).bind("p1").to("foo").build(), 1L));
@@ -194,20 +194,22 @@ public void testInsertAutoCommit() throws IOException, InterruptedException {
List executeSqlRequests =
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
- .filter(
- request ->
- request.getSql().equals(sql) || request.getSql().equals(describeParamsSql))
+ .filter(request -> request.getSql().equals(sql))
.collect(Collectors.toList());
assertEquals(2, executeSqlRequests.size());
ExecuteSqlRequest describeRequest = executeSqlRequests.get(0);
- assertEquals(describeParamsSql, describeRequest.getSql());
- assertFalse(describeRequest.hasTransaction());
+ assertEquals(sql, describeRequest.getSql());
+ assertTrue(describeRequest.hasTransaction());
+ assertTrue(describeRequest.getTransaction().hasBegin());
+ assertTrue(describeRequest.getTransaction().getBegin().hasReadWrite());
+
ExecuteSqlRequest executeRequest = executeSqlRequests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(1, executeRequest.getParamTypesCount());
- assertTrue(executeRequest.getTransaction().hasBegin());
- assertTrue(executeRequest.getTransaction().getBegin().hasReadWrite());
- assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
+ // TODO: Enable when node-postgres 8.9 has been released.
+ // assertTrue(executeRequest.getTransaction().hasBegin());
+ // assertTrue(executeRequest.getTransaction().getBegin().hasReadWrite());
+ // assertEquals(2, mockSpanner.countRequestsOfType(CommitRequest.class));
}
@Test
@@ -216,15 +218,12 @@ public void testInsertAllTypes() throws IOException, InterruptedException {
"INSERT INTO AllTypes "
+ "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar, col_jsonb) "
+ "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
- String describeParamsSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 from "
- + "(select col_bigint=$1, col_bool=$2, col_bytea=$3, col_float8=$4, col_int=$5, col_numeric=$6, col_timestamptz=$7, col_date=$8, col_varchar=$9, col_jsonb=$10 from AllTypes) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeParamsSql),
- com.google.spanner.v1.ResultSet.newBuilder()
+ Statement.of(sql),
+ ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.BOOL,
@@ -236,6 +235,7 @@ public void testInsertAllTypes() throws IOException, InterruptedException {
TypeCode.DATE,
TypeCode.STRING,
TypeCode.JSON)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
StatementResult updateResult =
StatementResult.update(
@@ -270,20 +270,22 @@ public void testInsertAllTypes() throws IOException, InterruptedException {
List executeSqlRequests =
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
- .filter(
- request ->
- request.getSql().equals(sql) || request.getSql().equals(describeParamsSql))
+ .filter(request -> request.getSql().equals(sql))
.collect(Collectors.toList());
assertEquals(2, executeSqlRequests.size());
ExecuteSqlRequest describeRequest = executeSqlRequests.get(0);
- assertEquals(describeParamsSql, describeRequest.getSql());
- assertFalse(describeRequest.hasTransaction());
+ assertEquals(sql, describeRequest.getSql());
+ assertTrue(describeRequest.hasTransaction());
+ assertTrue(describeRequest.getTransaction().hasBegin());
+ assertTrue(describeRequest.getTransaction().getBegin().hasReadWrite());
+
ExecuteSqlRequest executeRequest = executeSqlRequests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(10, executeRequest.getParamTypesCount());
- assertTrue(executeRequest.getTransaction().hasBegin());
- assertTrue(executeRequest.getTransaction().getBegin().hasReadWrite());
- assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
+ // TODO: Enable once node-postgres 8.9 is released.
+ // assertTrue(executeRequest.getTransaction().hasBegin());
+ // assertTrue(executeRequest.getTransaction().getBegin().hasReadWrite());
+ // assertEquals(2, mockSpanner.countRequestsOfType(CommitRequest.class));
}
@Test
@@ -341,15 +343,12 @@ public void testInsertAllTypesPreparedStatement() throws IOException, Interrupte
"INSERT INTO AllTypes "
+ "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar, col_jsonb) "
+ "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
- String describeParamsSql =
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 from "
- + "(select col_bigint=$1, col_bool=$2, col_bytea=$3, col_float8=$4, col_int=$5, col_numeric=$6, col_timestamptz=$7, col_date=$8, col_varchar=$9, col_jsonb=$10 from AllTypes) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeParamsSql),
- com.google.spanner.v1.ResultSet.newBuilder()
+ Statement.of(sql),
+ ResultSet.newBuilder()
.setMetadata(
- createMetadata(
+ createParameterTypesMetadata(
ImmutableList.of(
TypeCode.INT64,
TypeCode.BOOL,
@@ -361,6 +360,7 @@ public void testInsertAllTypesPreparedStatement() throws IOException, Interrupte
TypeCode.DATE,
TypeCode.STRING,
TypeCode.JSON)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
StatementResult updateResult =
StatementResult.update(
@@ -436,20 +436,22 @@ public void testInsertAllTypesPreparedStatement() throws IOException, Interrupte
List executeSqlRequests =
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
- .filter(
- request ->
- request.getSql().equals(sql) || request.getSql().equals(describeParamsSql))
+ .filter(request -> request.getSql().equals(sql))
.collect(Collectors.toList());
assertEquals(3, executeSqlRequests.size());
ExecuteSqlRequest describeRequest = executeSqlRequests.get(0);
- assertEquals(describeParamsSql, describeRequest.getSql());
- assertFalse(describeRequest.hasTransaction());
+ assertEquals(sql, describeRequest.getSql());
+ assertTrue(describeRequest.hasTransaction());
+ assertTrue(describeRequest.getTransaction().hasBegin());
+ assertTrue(describeRequest.getTransaction().getBegin().hasReadWrite());
+
ExecuteSqlRequest executeRequest = executeSqlRequests.get(1);
assertEquals(sql, executeRequest.getSql());
assertEquals(10, executeRequest.getParamTypesCount());
- assertTrue(executeRequest.getTransaction().hasBegin());
- assertTrue(executeRequest.getTransaction().getBegin().hasReadWrite());
- assertEquals(2, mockSpanner.countRequestsOfType(CommitRequest.class));
+ // TODO: Enable once node-postgres 8.9 is released.
+ // assertTrue(executeRequest.getTransaction().hasBegin());
+ // assertTrue(executeRequest.getTransaction().getBegin().hasReadWrite());
+ // assertEquals(3, mockSpanner.countRequestsOfType(CommitRequest.class));
}
@Test
@@ -603,12 +605,12 @@ public void testCopyFrom() throws Exception {
@Test
public void testDmlBatch() throws Exception {
String sql = "INSERT INTO users(name) VALUES($1)";
- String describeParamsSql = "select $1 from (select name=$1 from users) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeParamsSql),
- com.google.spanner.v1.ResultSet.newBuilder()
- .setMetadata(createMetadata(ImmutableList.of(TypeCode.STRING)))
+ Statement.of(sql),
+ ResultSet.newBuilder()
+ .setMetadata(createParameterTypesMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
.build()));
mockSpanner.putStatementResult(
StatementResult.update(Statement.newBuilder(sql).bind("p1").to("foo").build(), 1L));
@@ -621,14 +623,14 @@ public void testDmlBatch() throws Exception {
List executeSqlRequests =
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
- .filter(
- request ->
- request.getSql().equals(sql) || request.getSql().equals(describeParamsSql))
+ .filter(request -> request.getSql().equals(sql))
.collect(Collectors.toList());
assertEquals(1, executeSqlRequests.size());
ExecuteSqlRequest describeRequest = executeSqlRequests.get(0);
- assertEquals(describeParamsSql, describeRequest.getSql());
- assertFalse(describeRequest.hasTransaction());
+ assertEquals(sql, describeRequest.getSql());
+ assertTrue(describeRequest.hasTransaction());
+ assertTrue(describeRequest.getTransaction().hasBegin());
+ assertTrue(describeRequest.getTransaction().getBegin().hasReadWrite());
List batchDmlRequests =
mockSpanner.getRequestsOfType(ExecuteBatchDmlRequest.class);
@@ -645,7 +647,9 @@ public void testDmlBatch() throws Exception {
expectedValues[i],
request.getStatements(i).getParams().getFieldsMap().get("p1").getStringValue());
}
- assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
+ // We get two commits, because PGAdapter auto-describes the DML statement in a separate
+ // transaction if the auto-describe happens during a DML batch.
+ assertEquals(2, mockSpanner.countRequestsOfType(CommitRequest.class));
}
@Test
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/nodejs/TypeORMMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/nodejs/TypeORMMockServerTest.java
index 1269347114..93e2414063 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/nodejs/TypeORMMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/nodejs/TypeORMMockServerTest.java
@@ -18,16 +18,22 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import com.google.cloud.ByteArray;
+import com.google.cloud.Date;
import com.google.cloud.Timestamp;
import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
import com.google.cloud.spanner.Statement;
import com.google.cloud.spanner.pgadapter.AbstractMockServerTest;
+import com.google.common.collect.ImmutableList;
import com.google.protobuf.ListValue;
import com.google.protobuf.Value;
+import com.google.spanner.v1.BeginTransactionRequest;
import com.google.spanner.v1.CommitRequest;
import com.google.spanner.v1.ExecuteSqlRequest;
+import com.google.spanner.v1.ExecuteSqlRequest.QueryMode;
import com.google.spanner.v1.ResultSet;
import com.google.spanner.v1.ResultSetMetadata;
+import com.google.spanner.v1.ResultSetStats;
import com.google.spanner.v1.StructType;
import com.google.spanner.v1.StructType.Field;
import com.google.spanner.v1.Type;
@@ -88,6 +94,22 @@ public void testFindOneUser() throws IOException, InterruptedException {
mockSpanner.putStatementResult(
StatementResult.query(
Statement.of(sql),
+ ResultSet.newBuilder()
+ .setMetadata(
+ USERS_METADATA
+ .toBuilder()
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
+ .build())
+ .build()))
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.newBuilder(sql).bind("p1").to(1L).build(),
ResultSet.newBuilder()
.setMetadata(USERS_METADATA)
.addRows(
@@ -107,21 +129,34 @@ public void testFindOneUser() throws IOException, InterruptedException {
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
.filter(request -> request.getSql().equals(sql))
.collect(Collectors.toList());
- assertEquals(1, executeSqlRequests.size());
- ExecuteSqlRequest request = executeSqlRequests.get(0);
+ assertEquals(2, executeSqlRequests.size());
+ ExecuteSqlRequest describeRequest = executeSqlRequests.get(0);
+ ExecuteSqlRequest executeRequest = executeSqlRequests.get(1);
// The TypeORM PostgreSQL driver sends both a Flush and a Sync message. The Flush message does
// a look-ahead to determine if the next message is a Sync, and if it is, executes a Sync on the
- // backend connection. This is a lot more efficient, as it means that we can use single-use
- // read-only transactions for single queries.
+ // backend connection. This is a lot more efficient, as it means that we can use a read-only
+ // transaction for transactions that only contains queries.
// There is however no guarantee that the server will see the Sync message in time to do this
- // optimization, so in some cases the single query will be using a read/write transaction.
+ // optimization, so in some cases the single query will be using a read/write transaction, as we
+ // don't know what might be following the current query.
+ // This behavior in node-postgres has been fixed in
+ // https://github.com/brianc/node-postgres/pull/2842,
+ // but has not yet been released.
int commitRequestCount = mockSpanner.countRequestsOfType(CommitRequest.class);
if (commitRequestCount == 0) {
- assertTrue(request.getTransaction().hasSingleUse());
- assertTrue(request.getTransaction().getSingleUse().hasReadOnly());
+ assertEquals(1, mockSpanner.countRequestsOfType(BeginTransactionRequest.class));
+ assertTrue(
+ mockSpanner
+ .getRequestsOfType(BeginTransactionRequest.class)
+ .get(0)
+ .getOptions()
+ .hasReadOnly());
+ assertTrue(describeRequest.getTransaction().hasId());
+ assertTrue(executeRequest.getTransaction().hasId());
} else if (commitRequestCount == 1) {
- assertTrue(request.getTransaction().hasBegin());
- assertTrue(request.getTransaction().getBegin().hasReadWrite());
+ assertTrue(describeRequest.getTransaction().hasBegin());
+ assertTrue(describeRequest.getTransaction().getBegin().hasReadWrite());
+ assertTrue(executeRequest.getTransaction().hasId());
} else {
fail("Invalid commit count: " + commitRequestCount);
}
@@ -135,22 +170,79 @@ public void testCreateUser() throws IOException, InterruptedException {
+ "FROM \"user\" \"User\" WHERE \"User\".\"id\" IN ($1)";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(existsSql), ResultSet.newBuilder().setMetadata(USERS_METADATA).build()));
+ Statement.of(existsSql),
+ ResultSet.newBuilder()
+ .setMetadata(
+ USERS_METADATA
+ .toBuilder()
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
+ .build())
+ .build()))
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.newBuilder(existsSql).bind("p1").to(1L).build(),
+ ResultSet.newBuilder().setMetadata(USERS_METADATA).build()));
String insertSql =
"INSERT INTO \"user\"(\"id\", \"firstName\", \"lastName\", \"age\") VALUES ($1, $2, $3, $4)";
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(insertSql), 1L));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of(insertSql),
+ ResultSet.newBuilder()
+ .setMetadata(
+ createParameterTypesMetadata(
+ ImmutableList.of(
+ TypeCode.INT64, TypeCode.STRING, TypeCode.STRING, TypeCode.INT64)))
+ .setStats(ResultSetStats.newBuilder().build())
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.update(
+ Statement.newBuilder(insertSql)
+ .bind("p1")
+ .to(1L)
+ .bind("p2")
+ .to("Timber")
+ .bind("p3")
+ .to("Saw")
+ .bind("p4")
+ .to(25L)
+ .build(),
+ 1L));
String sql =
"SELECT \"User\".\"id\" AS \"User_id\", \"User\".\"firstName\" AS \"User_firstName\", "
+ "\"User\".\"lastName\" AS \"User_lastName\", \"User\".\"age\" AS \"User_age\" "
+ "FROM \"user\" \"User\" WHERE (\"User\".\"firstName\" = $1 AND \"User\".\"lastName\" = $2) "
+ "LIMIT 1";
- // The parameter is sent as an untyped parameter, and therefore not included in the statement
- // lookup on the mock server, hence the Statement.of(sql) instead of building a statement that
- // does include the parameter value.
mockSpanner.putStatementResult(
StatementResult.query(
Statement.of(sql),
+ ResultSet.newBuilder()
+ .setMetadata(
+ USERS_METADATA
+ .toBuilder()
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.STRING).build())
+ .build())
+ .addFields(
+ Field.newBuilder()
+ .setName("p2")
+ .setType(Type.newBuilder().setCode(TypeCode.STRING).build())
+ .build())
+ .build()))
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.newBuilder(sql).bind("p1").to("Timber").bind("p2").to("Saw").build(),
ResultSet.newBuilder()
.setMetadata(USERS_METADATA)
.addRows(
@@ -174,25 +266,27 @@ public void testCreateUser() throws IOException, InterruptedException {
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
.filter(request -> request.getSql().equals(existsSql))
.collect(Collectors.toList());
- assertEquals(1, checkExistsRequests.size());
- ExecuteSqlRequest checkExistsRequest = checkExistsRequests.get(0);
- if (checkExistsRequest.getTransaction().hasSingleUse()) {
- assertTrue(checkExistsRequest.getTransaction().getSingleUse().hasReadOnly());
- } else if (checkExistsRequest.getTransaction().hasBegin()) {
- assertTrue(checkExistsRequest.getTransaction().getBegin().hasReadWrite());
+ assertEquals(2, checkExistsRequests.size());
+ ExecuteSqlRequest describeCheckExistsRequest = checkExistsRequests.get(0);
+ ExecuteSqlRequest executeCheckExistsRequest = checkExistsRequests.get(1);
+ assertEquals(QueryMode.PLAN, describeCheckExistsRequest.getQueryMode());
+ assertEquals(QueryMode.NORMAL, executeCheckExistsRequest.getQueryMode());
+ if (describeCheckExistsRequest.getTransaction().hasBegin()) {
expectedCommitCount++;
- } else {
- fail("missing or invalid transaction option: " + checkExistsRequest.getTransaction());
}
List insertRequests =
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
.filter(request -> request.getSql().equals(insertSql))
.collect(Collectors.toList());
- assertEquals(1, insertRequests.size());
- ExecuteSqlRequest insertRequest = insertRequests.get(0);
- assertTrue(insertRequest.getTransaction().hasBegin());
- assertTrue(insertRequest.getTransaction().getBegin().hasReadWrite());
+ assertEquals(2, insertRequests.size());
+ ExecuteSqlRequest describeInsertRequest = insertRequests.get(0);
+ assertEquals(QueryMode.PLAN, describeInsertRequest.getQueryMode());
+ assertTrue(describeInsertRequest.getTransaction().hasBegin());
+ assertTrue(describeInsertRequest.getTransaction().getBegin().hasReadWrite());
+ ExecuteSqlRequest executeInsertRequest = insertRequests.get(1);
+ assertEquals(QueryMode.NORMAL, executeInsertRequest.getQueryMode());
+ assertTrue(executeInsertRequest.getTransaction().hasId());
expectedCommitCount++;
// Loading the user after having saved it will be done in a single-use read-only transaction.
@@ -200,15 +294,13 @@ public void testCreateUser() throws IOException, InterruptedException {
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
.filter(request -> request.getSql().equals(sql))
.collect(Collectors.toList());
- assertEquals(1, loadRequests.size());
- ExecuteSqlRequest loadRequest = loadRequests.get(0);
- if (loadRequest.getTransaction().hasSingleUse()) {
- assertTrue(loadRequest.getTransaction().getSingleUse().hasReadOnly());
- } else if (loadRequest.getTransaction().hasBegin()) {
- assertTrue(loadRequest.getTransaction().getBegin().hasReadWrite());
+ assertEquals(2, loadRequests.size());
+ ExecuteSqlRequest describeLoadRequest = loadRequests.get(0);
+ assertEquals(QueryMode.PLAN, describeLoadRequest.getQueryMode());
+ ExecuteSqlRequest executeLoadRequest = loadRequests.get(1);
+ assertEquals(QueryMode.NORMAL, executeLoadRequest.getQueryMode());
+ if (describeLoadRequest.getTransaction().hasBegin()) {
expectedCommitCount++;
- } else {
- fail("missing or invalid transaction option: " + loadRequest.getTransaction());
}
assertEquals(expectedCommitCount, mockSpanner.countRequestsOfType(CommitRequest.class));
}
@@ -222,6 +314,23 @@ public void testUpdateUser() throws IOException, InterruptedException {
mockSpanner.putStatementResult(
StatementResult.query(
Statement.of(loadSql),
+ ResultSet.newBuilder()
+ .setMetadata(
+ USERS_METADATA
+ .toBuilder()
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
+ .build())
+ .build())
+ .build())
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.newBuilder(loadSql).bind("p1").to(1L).build(),
ResultSet.newBuilder()
.setMetadata(USERS_METADATA)
.addRows(
@@ -232,6 +341,7 @@ public void testUpdateUser() throws IOException, InterruptedException {
.addValues(Value.newBuilder().setStringValue("25").build())
.build())
.build()));
+
String existsSql =
"SELECT \"User\".\"id\" AS \"User_id\", \"User\".\"firstName\" AS \"User_firstName\", "
+ "\"User\".\"lastName\" AS \"User_lastName\", \"User\".\"age\" AS \"User_age\" "
@@ -239,6 +349,23 @@ public void testUpdateUser() throws IOException, InterruptedException {
mockSpanner.putStatementResult(
StatementResult.query(
Statement.of(existsSql),
+ ResultSet.newBuilder()
+ .setMetadata(
+ USERS_METADATA
+ .toBuilder()
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
+ .build())
+ .build())
+ .build())
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.newBuilder(existsSql).bind("p1").to(1L).build(),
ResultSet.newBuilder()
.setMetadata(USERS_METADATA)
.addRows(
@@ -252,7 +379,29 @@ public void testUpdateUser() throws IOException, InterruptedException {
String updateSql =
"UPDATE \"user\" SET \"firstName\" = $1, \"lastName\" = $2, \"age\" = $3 WHERE \"id\" IN ($4)";
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(updateSql), 1L));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of(updateSql),
+ ResultSet.newBuilder()
+ .setMetadata(
+ createParameterTypesMetadata(
+ ImmutableList.of(
+ TypeCode.STRING, TypeCode.STRING, TypeCode.INT64, TypeCode.INT64)))
+ .setStats(ResultSetStats.newBuilder().build())
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.update(
+ Statement.newBuilder(updateSql)
+ .bind("p1")
+ .to("Lumber")
+ .bind("p2")
+ .to("Jack")
+ .bind("p3")
+ .to(45L)
+ .bind("p4")
+ .to(1L)
+ .build(),
+ 1L));
String output = runTest("updateUser", pgServer.getLocalPort());
@@ -266,40 +415,38 @@ public void testUpdateUser() throws IOException, InterruptedException {
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
.filter(request -> request.getSql().equals(loadSql))
.collect(Collectors.toList());
- assertEquals(1, loadRequests.size());
- ExecuteSqlRequest loadRequest = loadRequests.get(0);
- if (loadRequest.getTransaction().hasSingleUse()) {
- assertTrue(loadRequest.getTransaction().getSingleUse().hasReadOnly());
- } else if (loadRequest.getTransaction().hasBegin()) {
- assertTrue(loadRequest.getTransaction().getBegin().hasReadWrite());
+ assertEquals(2, loadRequests.size());
+ ExecuteSqlRequest describeLoadRequest = loadRequests.get(0);
+ assertEquals(QueryMode.PLAN, describeLoadRequest.getQueryMode());
+ ExecuteSqlRequest executeLoadRequest = loadRequests.get(1);
+ assertEquals(QueryMode.NORMAL, executeLoadRequest.getQueryMode());
+ if (describeLoadRequest.getTransaction().hasBegin()) {
expectedCommitCount++;
- } else {
- fail("missing or invalid transaction option: " + loadRequest.getTransaction());
}
List checkExistsRequests =
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
.filter(request -> request.getSql().equals(existsSql))
.collect(Collectors.toList());
- assertEquals(1, checkExistsRequests.size());
- ExecuteSqlRequest checkExistsRequest = checkExistsRequests.get(0);
- if (checkExistsRequest.getTransaction().hasSingleUse()) {
- assertTrue(checkExistsRequest.getTransaction().getSingleUse().hasReadOnly());
- } else if (checkExistsRequest.getTransaction().hasBegin()) {
- assertTrue(checkExistsRequest.getTransaction().getBegin().hasReadWrite());
+ assertEquals(2, checkExistsRequests.size());
+ ExecuteSqlRequest describeCheckExistsRequest = checkExistsRequests.get(0);
+ assertEquals(QueryMode.PLAN, describeCheckExistsRequest.getQueryMode());
+ ExecuteSqlRequest executeCheckExistsRequest = checkExistsRequests.get(1);
+ assertEquals(QueryMode.NORMAL, executeCheckExistsRequest.getQueryMode());
+ if (describeCheckExistsRequest.getTransaction().hasBegin()) {
expectedCommitCount++;
- } else {
- fail("missing or invalid transaction option: " + checkExistsRequest.getTransaction());
}
List updateRequests =
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
.filter(request -> request.getSql().equals(updateSql))
.collect(Collectors.toList());
- assertEquals(1, updateRequests.size());
- ExecuteSqlRequest updateRequest = updateRequests.get(0);
- assertTrue(updateRequest.getTransaction().hasBegin());
- assertTrue(updateRequest.getTransaction().getBegin().hasReadWrite());
+ assertEquals(2, updateRequests.size());
+ ExecuteSqlRequest describeUpdateRequest = updateRequests.get(0);
+ ExecuteSqlRequest executeUpdateRequest = updateRequests.get(1);
+ assertTrue(describeUpdateRequest.getTransaction().hasBegin());
+ assertTrue(describeUpdateRequest.getTransaction().getBegin().hasReadWrite());
+ assertTrue(executeUpdateRequest.getTransaction().hasId());
expectedCommitCount++;
assertEquals(expectedCommitCount, mockSpanner.countRequestsOfType(CommitRequest.class));
@@ -314,6 +461,22 @@ public void testDeleteUser() throws IOException, InterruptedException {
mockSpanner.putStatementResult(
StatementResult.query(
Statement.of(loadSql),
+ ResultSet.newBuilder()
+ .setMetadata(
+ USERS_METADATA
+ .toBuilder()
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
+ .build())
+ .build()))
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.newBuilder(loadSql).bind("p1").to(1L).build(),
ResultSet.newBuilder()
.setMetadata(USERS_METADATA)
.addRows(
@@ -324,6 +487,7 @@ public void testDeleteUser() throws IOException, InterruptedException {
.addValues(Value.newBuilder().setStringValue("25").build())
.build())
.build()));
+
String existsSql =
"SELECT \"User\".\"id\" AS \"User_id\", \"User\".\"firstName\" AS \"User_firstName\", "
+ "\"User\".\"lastName\" AS \"User_lastName\", \"User\".\"age\" AS \"User_age\" "
@@ -331,6 +495,22 @@ public void testDeleteUser() throws IOException, InterruptedException {
mockSpanner.putStatementResult(
StatementResult.query(
Statement.of(existsSql),
+ ResultSet.newBuilder()
+ .setMetadata(
+ USERS_METADATA
+ .toBuilder()
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
+ .build())
+ .build()))
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.newBuilder(existsSql).bind("p1").to(1L).build(),
ResultSet.newBuilder()
.setMetadata(USERS_METADATA)
.addRows(
@@ -343,7 +523,15 @@ public void testDeleteUser() throws IOException, InterruptedException {
.build()));
String deleteSql = "DELETE FROM \"user\" WHERE \"id\" = $1";
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(deleteSql), 1L));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of(deleteSql),
+ ResultSet.newBuilder()
+ .setMetadata(createParameterTypesMetadata(ImmutableList.of(TypeCode.INT64)))
+ .setStats(ResultSetStats.newBuilder().build())
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.update(Statement.newBuilder(deleteSql).bind("p1").to(1L).build(), 1L));
String output = runTest("deleteUser", pgServer.getLocalPort());
@@ -357,40 +545,39 @@ public void testDeleteUser() throws IOException, InterruptedException {
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
.filter(request -> request.getSql().equals(loadSql))
.collect(Collectors.toList());
- assertEquals(1, loadRequests.size());
- ExecuteSqlRequest loadRequest = loadRequests.get(0);
- if (loadRequest.getTransaction().hasSingleUse()) {
- assertTrue(loadRequest.getTransaction().getSingleUse().hasReadOnly());
- } else if (loadRequest.getTransaction().hasBegin()) {
- assertTrue(loadRequest.getTransaction().getBegin().hasReadWrite());
+ assertEquals(2, loadRequests.size());
+ ExecuteSqlRequest describeLoadRequest = loadRequests.get(0);
+ assertEquals(QueryMode.PLAN, describeLoadRequest.getQueryMode());
+ ExecuteSqlRequest executeLoadRequest = loadRequests.get(1);
+ assertEquals(QueryMode.NORMAL, executeLoadRequest.getQueryMode());
+ if (describeLoadRequest.getTransaction().hasBegin()) {
expectedCommitCount++;
- } else {
- fail("missing or invalid transaction option: " + loadRequest.getTransaction());
}
List checkExistsRequests =
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
.filter(request -> request.getSql().equals(existsSql))
.collect(Collectors.toList());
- assertEquals(1, checkExistsRequests.size());
- ExecuteSqlRequest checkExistsRequest = checkExistsRequests.get(0);
- if (checkExistsRequest.getTransaction().hasSingleUse()) {
- assertTrue(checkExistsRequest.getTransaction().getSingleUse().hasReadOnly());
- } else if (checkExistsRequest.getTransaction().hasBegin()) {
- assertTrue(checkExistsRequest.getTransaction().getBegin().hasReadWrite());
+ assertEquals(2, checkExistsRequests.size());
+ ExecuteSqlRequest describeCheckExistsRequest = checkExistsRequests.get(0);
+ assertEquals(QueryMode.PLAN, describeCheckExistsRequest.getQueryMode());
+ ExecuteSqlRequest executeCheckExistsRequest = checkExistsRequests.get(1);
+ assertEquals(QueryMode.NORMAL, executeCheckExistsRequest.getQueryMode());
+ if (describeCheckExistsRequest.getTransaction().hasBegin()) {
expectedCommitCount++;
- } else {
- fail("missing or invalid transaction option: " + checkExistsRequest.getTransaction());
}
List deleteRequests =
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
.filter(request -> request.getSql().equals(deleteSql))
.collect(Collectors.toList());
- assertEquals(1, deleteRequests.size());
- ExecuteSqlRequest deleteRequest = deleteRequests.get(0);
- assertTrue(deleteRequest.getTransaction().hasBegin());
- assertTrue(deleteRequest.getTransaction().getBegin().hasReadWrite());
+ assertEquals(2, deleteRequests.size());
+ ExecuteSqlRequest describeDeleteRequest = deleteRequests.get(0);
+ assertEquals(QueryMode.PLAN, describeDeleteRequest.getQueryMode());
+ ExecuteSqlRequest executeDeleteRequest = deleteRequests.get(1);
+ assertEquals(QueryMode.NORMAL, executeDeleteRequest.getQueryMode());
+ assertTrue(describeDeleteRequest.getTransaction().hasBegin());
+ assertTrue(describeDeleteRequest.getTransaction().getBegin().hasReadWrite());
expectedCommitCount++;
assertEquals(expectedCommitCount, mockSpanner.countRequestsOfType(CommitRequest.class));
@@ -407,7 +594,26 @@ public void testFindOneAllTypes() throws IOException, InterruptedException {
+ "FROM \"all_types\" \"AllTypes\" "
+ "WHERE (\"AllTypes\".\"col_bigint\" = $1) LIMIT 1";
mockSpanner.putStatementResult(
- StatementResult.query(Statement.of(sql), createAllTypesResultSet("AllTypes_")));
+ StatementResult.query(
+ Statement.of(sql),
+ ResultSet.newBuilder()
+ .setMetadata(
+ createAllTypesResultSetMetadata("AllTypes_")
+ .toBuilder()
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
+ .build())
+ .build())
+ .build())
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.newBuilder(sql).bind("p1").to(1L).build(),
+ createAllTypesResultSet("AllTypes_")));
String output = runTest("findOneAllTypes", pgServer.getLocalPort());
@@ -432,15 +638,13 @@ public void testFindOneAllTypes() throws IOException, InterruptedException {
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
.filter(request -> request.getSql().equals(sql))
.collect(Collectors.toList());
- assertEquals(1, executeSqlRequests.size());
- ExecuteSqlRequest request = executeSqlRequests.get(0);
- if (request.getTransaction().hasSingleUse()) {
- assertTrue(request.getTransaction().getSingleUse().hasReadOnly());
- } else if (request.getTransaction().hasBegin()) {
- assertTrue(request.getTransaction().getBegin().hasReadWrite());
+ assertEquals(2, executeSqlRequests.size());
+ ExecuteSqlRequest describeRequest = executeSqlRequests.get(0);
+ assertEquals(QueryMode.PLAN, describeRequest.getQueryMode());
+ ExecuteSqlRequest executeRequest = executeSqlRequests.get(1);
+ assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
+ if (describeRequest.getTransaction().hasBegin()) {
expectedCommitCount++;
- } else {
- fail("missing or invalid transaction option: " + request.getTransaction());
}
assertEquals(expectedCommitCount, mockSpanner.countRequestsOfType(CommitRequest.class));
}
@@ -458,6 +662,23 @@ public void testCreateAllTypes() throws IOException, InterruptedException {
mockSpanner.putStatementResult(
StatementResult.query(
Statement.of(existsSql),
+ ResultSet.newBuilder()
+ .setMetadata(
+ createAllTypesResultSetMetadata("AllTypes_")
+ .toBuilder()
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
+ .build())
+ .build())
+ .build())
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.newBuilder(existsSql).bind("p1").to(2L).build(),
ResultSet.newBuilder()
.setMetadata(createAllTypesResultSetMetadata("AllTypes_"))
.build()));
@@ -466,7 +687,51 @@ public void testCreateAllTypes() throws IOException, InterruptedException {
"INSERT INTO \"all_types\""
+ "(\"col_bigint\", \"col_bool\", \"col_bytea\", \"col_float8\", \"col_int\", \"col_numeric\", \"col_timestamptz\", \"col_date\", \"col_varchar\", \"col_jsonb\") "
+ "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(insertSql), 1L));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of(insertSql),
+ ResultSet.newBuilder()
+ .setMetadata(
+ createParameterTypesMetadata(
+ ImmutableList.of(
+ TypeCode.INT64,
+ TypeCode.BOOL,
+ TypeCode.BYTES,
+ TypeCode.FLOAT64,
+ TypeCode.INT64,
+ TypeCode.NUMERIC,
+ TypeCode.TIMESTAMP,
+ TypeCode.DATE,
+ TypeCode.STRING,
+ TypeCode.JSON)))
+ .setStats(ResultSetStats.newBuilder().build())
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.update(
+ Statement.newBuilder(insertSql)
+ .bind("p1")
+ .to(2L)
+ .bind("p2")
+ .to(true)
+ .bind("p3")
+ .to(ByteArray.copyFrom("some random string"))
+ .bind("p4")
+ .to(0.123456789d)
+ .bind("p5")
+ .to(123456789L)
+ .bind("p6")
+ .to(com.google.cloud.spanner.Value.pgNumeric("234.54235"))
+ .bind("p7")
+ .to(Timestamp.parseTimestamp("2022-07-22T18:15:42.011Z"))
+ .bind("p8")
+ .to(Date.parseDate("2022-07-22"))
+ .bind("p9")
+ .to("some random string")
+ // TODO: Change to JSONB
+ .bind("p10")
+ .to("{\"key\":\"value\"}")
+ .build(),
+ 1L));
String output = runTest("createAllTypes", pgServer.getLocalPort());
@@ -476,34 +741,42 @@ public void testCreateAllTypes() throws IOException, InterruptedException {
mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).stream()
.filter(request -> request.getSql().equals(insertSql))
.collect(Collectors.toList());
- assertEquals(1, insertRequests.size());
- ExecuteSqlRequest insertRequest = insertRequests.get(0);
- assertTrue(insertRequest.getTransaction().hasBegin());
- assertTrue(insertRequest.getTransaction().getBegin().hasReadWrite());
-
- // The NodeJS PostgreSQL driver sends parameters without any type information to the backend.
- // This means that all parameters are sent as untyped string values.
- assertEquals(0, insertRequest.getParamTypesMap().size());
- assertEquals(10, insertRequest.getParams().getFieldsCount());
- assertEquals("2", insertRequest.getParams().getFieldsMap().get("p1").getStringValue());
- assertEquals("true", insertRequest.getParams().getFieldsMap().get("p2").getStringValue());
+ assertEquals(2, insertRequests.size());
+ ExecuteSqlRequest describeInsertRequest = insertRequests.get(0);
+ assertEquals(QueryMode.PLAN, describeInsertRequest.getQueryMode());
+ ExecuteSqlRequest executeInsertRequest = insertRequests.get(1);
+ assertEquals(QueryMode.NORMAL, executeInsertRequest.getQueryMode());
+ assertTrue(describeInsertRequest.getTransaction().hasBegin());
+ assertTrue(describeInsertRequest.getTransaction().getBegin().hasReadWrite());
+ assertTrue(executeInsertRequest.getTransaction().hasId());
+
+ assertEquals(10, executeInsertRequest.getParamTypesMap().size());
+ assertEquals(10, executeInsertRequest.getParams().getFieldsCount());
+ assertEquals("2", executeInsertRequest.getParams().getFieldsMap().get("p1").getStringValue());
+ assertTrue(executeInsertRequest.getParams().getFieldsMap().get("p2").getBoolValue());
assertEquals(
"c29tZSByYW5kb20gc3RyaW5n",
- insertRequest.getParams().getFieldsMap().get("p3").getStringValue());
+ executeInsertRequest.getParams().getFieldsMap().get("p3").getStringValue());
+ assertEquals(
+ 0.123456789d,
+ executeInsertRequest.getParams().getFieldsMap().get("p4").getNumberValue(),
+ 0.0d);
+ assertEquals(
+ "123456789", executeInsertRequest.getParams().getFieldsMap().get("p5").getStringValue());
assertEquals(
- "0.123456789", insertRequest.getParams().getFieldsMap().get("p4").getStringValue());
- assertEquals("123456789", insertRequest.getParams().getFieldsMap().get("p5").getStringValue());
- assertEquals("234.54235", insertRequest.getParams().getFieldsMap().get("p6").getStringValue());
+ "234.54235", executeInsertRequest.getParams().getFieldsMap().get("p6").getStringValue());
assertEquals(
Timestamp.parseTimestamp("2022-07-22T20:15:42.011+02:00"),
Timestamp.parseTimestamp(
- insertRequest.getParams().getFieldsMap().get("p7").getStringValue()));
- assertEquals("2022-07-22", insertRequest.getParams().getFieldsMap().get("p8").getStringValue());
+ executeInsertRequest.getParams().getFieldsMap().get("p7").getStringValue()));
assertEquals(
- "some random string", insertRequest.getParams().getFieldsMap().get("p9").getStringValue());
+ "2022-07-22", executeInsertRequest.getParams().getFieldsMap().get("p8").getStringValue());
+ assertEquals(
+ "some random string",
+ executeInsertRequest.getParams().getFieldsMap().get("p9").getStringValue());
assertEquals(
"{\"key\":\"value\"}",
- insertRequest.getParams().getFieldsMap().get("p10").getStringValue());
+ executeInsertRequest.getParams().getFieldsMap().get("p10").getStringValue());
}
@Test
@@ -520,13 +793,62 @@ public void testUpdateAllTypes() throws Exception {
StatementResult.query(Statement.of(sql), createAllTypesResultSet("AllTypes_")));
String updateSql =
"UPDATE \"all_types\" SET \"col_bigint\" = $1, \"col_bool\" = $2, \"col_bytea\" = $3, \"col_float8\" = $4, \"col_int\" = $5, \"col_numeric\" = $6, \"col_timestamptz\" = $7, \"col_date\" = $8, \"col_varchar\" = $9, \"col_jsonb\" = $10 WHERE \"col_bigint\" IN ($11)";
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of(updateSql),
+ ResultSet.newBuilder()
+ .setMetadata(
+ createParameterTypesMetadata(
+ ImmutableList.of(
+ TypeCode.INT64,
+ TypeCode.BOOL,
+ TypeCode.BYTES,
+ TypeCode.FLOAT64,
+ TypeCode.INT64,
+ TypeCode.NUMERIC,
+ TypeCode.TIMESTAMP,
+ TypeCode.DATE,
+ TypeCode.STRING,
+ TypeCode.JSON)))
+ .setStats(ResultSetStats.newBuilder().build())
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.update(
+ Statement.newBuilder(updateSql)
+ .bind("p1")
+ .to(1L)
+ .bind("p2")
+ .to(false)
+ .bind("p3")
+ .to(ByteArray.copyFrom("updated string"))
+ .bind("p4")
+ .to(1.23456789)
+ .bind("p5")
+ .to(987654321L)
+ .bind("p6")
+ .to(com.google.cloud.spanner.Value.pgNumeric("6.626"))
+ .bind("p7")
+ .to(Timestamp.parseTimestamp("2022-11-16T10:03:42.999Z"))
+ .bind("p8")
+ .to(Date.parseDate("2022-11-16"))
+ .bind("p9")
+ .to("some updated string")
+ // TODO: Change to JSONB
+ .bind("p10")
+ .to("{\"key\":\"updated-value\"}")
+ .build(),
+ 1L));
+
mockSpanner.putStatementResult(StatementResult.update(Statement.of(updateSql), 1L));
String output = runTest("updateAllTypes", pgServer.getLocalPort());
assertEquals("Updated one record\n", output);
- assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
+ // We get two commit requests, because the statement is auto-described the first time the update
+ // is executed. The auto-describe also runs in autocommit mode.
+ // TODO: Enable when node-postgres 8.9 has been released.
+ // assertEquals(2, mockSpanner.countRequestsOfType(CommitRequest.class));
assertEquals(4, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
ExecuteSqlRequest updateRequest = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(3);
assertEquals(updateSql, updateRequest.getSql());
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/parsers/ParserTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/parsers/ParserTest.java
index 56b537fa3e..a11e0be310 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/parsers/ParserTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/parsers/ParserTest.java
@@ -14,6 +14,7 @@
package com.google.cloud.spanner.pgadapter.parsers;
+import static com.google.cloud.spanner.pgadapter.parsers.Parser.toOid;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -32,8 +33,10 @@
import com.google.cloud.spanner.Type;
import com.google.cloud.spanner.Value;
import com.google.cloud.spanner.pgadapter.ProxyServer.DataFormat;
+import com.google.cloud.spanner.pgadapter.error.PGException;
import com.google.cloud.spanner.pgadapter.parsers.Parser.FormatCode;
import com.google.cloud.spanner.pgadapter.session.SessionState;
+import com.google.spanner.v1.TypeCode;
import java.math.BigDecimal;
import java.util.Arrays;
import org.junit.Test;
@@ -409,4 +412,42 @@ public void testNumericParsingNaN() {
assertEquals(value, parser.getItem());
validateCreateText(stringResult, Oid.NUMERIC, value);
}
+
+ @Test
+ public void testTypeToOid() {
+ assertEquals(Oid.INT8, toOid(createType(TypeCode.INT64)));
+ assertEquals(Oid.BOOL, toOid(createType(TypeCode.BOOL)));
+ assertEquals(Oid.VARCHAR, toOid(createType(TypeCode.STRING)));
+ assertEquals(Oid.JSONB, toOid(createType(TypeCode.JSON)));
+ assertEquals(Oid.FLOAT8, toOid(createType(TypeCode.FLOAT64)));
+ assertEquals(Oid.TIMESTAMPTZ, toOid(createType(TypeCode.TIMESTAMP)));
+ assertEquals(Oid.DATE, toOid(createType(TypeCode.DATE)));
+ assertEquals(Oid.NUMERIC, toOid(createType(TypeCode.NUMERIC)));
+ assertEquals(Oid.BYTEA, toOid(createType(TypeCode.BYTES)));
+
+ assertEquals(Oid.INT8_ARRAY, toOid(createArrayType(TypeCode.INT64)));
+ assertEquals(Oid.BOOL_ARRAY, toOid(createArrayType(TypeCode.BOOL)));
+ assertEquals(Oid.VARCHAR_ARRAY, toOid(createArrayType(TypeCode.STRING)));
+ assertEquals(Oid.JSONB_ARRAY, toOid(createArrayType(TypeCode.JSON)));
+ assertEquals(Oid.FLOAT8_ARRAY, toOid(createArrayType(TypeCode.FLOAT64)));
+ assertEquals(Oid.TIMESTAMPTZ_ARRAY, toOid(createArrayType(TypeCode.TIMESTAMP)));
+ assertEquals(Oid.DATE_ARRAY, toOid(createArrayType(TypeCode.DATE)));
+ assertEquals(Oid.NUMERIC_ARRAY, toOid(createArrayType(TypeCode.NUMERIC)));
+ assertEquals(Oid.BYTEA_ARRAY, toOid(createArrayType(TypeCode.BYTES)));
+
+ assertThrows(PGException.class, () -> toOid(createType(TypeCode.STRUCT)));
+ assertThrows(PGException.class, () -> toOid(createArrayType(TypeCode.ARRAY)));
+ assertThrows(PGException.class, () -> toOid(createArrayType(TypeCode.STRUCT)));
+ }
+
+ static com.google.spanner.v1.Type createType(TypeCode code) {
+ return com.google.spanner.v1.Type.newBuilder().setCode(code).build();
+ }
+
+ static com.google.spanner.v1.Type createArrayType(TypeCode code) {
+ return com.google.spanner.v1.Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(com.google.spanner.v1.Type.newBuilder().setCode(code).build())
+ .build();
+ }
}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/parsers/UnspecifiedParserTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/parsers/UnspecifiedParserTest.java
index 221bf548f2..93cbae291e 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/parsers/UnspecifiedParserTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/parsers/UnspecifiedParserTest.java
@@ -18,7 +18,9 @@
import static org.junit.Assert.assertNull;
import com.google.cloud.spanner.Value;
+import com.google.cloud.spanner.pgadapter.parsers.Parser.FormatCode;
import com.google.protobuf.NullValue;
+import java.nio.charset.StandardCharsets;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -42,5 +44,9 @@ public void testStringParse() {
.build()))
.stringParse());
assertNull(new UnspecifiedParser(null).stringParse());
+ assertEquals(
+ "test",
+ new UnspecifiedParser("test".getBytes(StandardCharsets.UTF_8), FormatCode.TEXT)
+ .stringParse());
}
}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/python/psycopg2/PythonBasicTests.java b/src/test/java/com/google/cloud/spanner/pgadapter/python/psycopg2/PythonBasicTests.java
index 2062ee94cd..21f01e819e 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/python/psycopg2/PythonBasicTests.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/python/psycopg2/PythonBasicTests.java
@@ -29,6 +29,7 @@
import com.google.spanner.v1.ExecuteSqlRequest.QueryMode;
import com.google.spanner.v1.ResultSet;
import com.google.spanner.v1.ResultSetMetadata;
+import com.google.spanner.v1.ResultSetStats;
import com.google.spanner.v1.StructType;
import com.google.spanner.v1.StructType.Field;
import com.google.spanner.v1.Type;
@@ -218,12 +219,13 @@ public void testPreparedInsertWithParameters() throws IOException, InterruptedEx
parameters.add("VALUE");
String sql = "INSERT INTO SOME_TABLE(COLUMN_NAME) VALUES ($1)";
- String describeParametersSql = "select $1 from (select COLUMN_NAME=$1 from SOME_TABLE) p";
mockSpanner.putStatementResult(
StatementResult.query(
- Statement.of(describeParametersSql),
- createResultSetWithOnlyMetadata(ImmutableList.of(TypeCode.STRING))));
- mockSpanner.putStatementResult(StatementResult.update(Statement.of(sql), 0));
+ Statement.of(sql),
+ ResultSet.newBuilder()
+ .setMetadata(createParameterTypesMetadata(ImmutableList.of(TypeCode.STRING)))
+ .setStats(ResultSetStats.newBuilder().build())
+ .build()));
mockSpanner.putStatementResult(
StatementResult.update(Statement.newBuilder(sql).bind("p1").to("VALUE").build(), 1));
@@ -233,20 +235,17 @@ public void testPreparedInsertWithParameters() throws IOException, InterruptedEx
String expectedOutput = "1\n1\n";
assertEquals(expectedOutput, actualOutput);
- // We receive 4 ExecuteSqlRequests:
- // 1. Describe parameters.
- // 2. Analyze the update statement.
- // 3. Execute the update statement twice.
+ // We receive 3 ExecuteSqlRequests:
+ // 1. Analyze the update statement.
+ // 2. Execute the update statement twice.
List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
- assertEquals(4, requests.size());
- assertEquals(describeParametersSql, requests.get(0).getSql());
+ assertEquals(3, requests.size());
+ assertEquals(sql, requests.get(0).getSql());
assertEquals(QueryMode.PLAN, requests.get(0).getQueryMode());
assertEquals(sql, requests.get(1).getSql());
- assertEquals(QueryMode.PLAN, requests.get(1).getQueryMode());
+ assertEquals(QueryMode.NORMAL, requests.get(1).getQueryMode());
assertEquals(sql, requests.get(2).getSql());
assertEquals(QueryMode.NORMAL, requests.get(2).getQueryMode());
- assertEquals(sql, requests.get(3).getSql());
- assertEquals(QueryMode.NORMAL, requests.get(3).getQueryMode());
// This is all executed in auto commit mode. That means that the analyzeUpdate call is also
// executed in auto commit mode, and is automatically committed.
assertEquals(3, mockSpanner.countRequestsOfType(CommitRequest.class));
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/statements/BackendConnectionTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/statements/BackendConnectionTest.java
index edd39d86ba..ca6958beba 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/statements/BackendConnectionTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/statements/BackendConnectionTest.java
@@ -59,6 +59,7 @@
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
+import java.util.function.Function;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -138,10 +139,12 @@ public void testExecuteStatementsInBatch() {
backendConnection.execute(
PARSER.parse(Statement.of("CREATE TABLE \"Foo\" (id bigint primary key)")),
- Statement.of("CREATE TABLE \"Foo\" (id bigint primary key)"));
+ Statement.of("CREATE TABLE \"Foo\" (id bigint primary key)"),
+ Function.identity());
backendConnection.execute(
PARSER.parse(Statement.of("CREATE TABLE bar (id bigint primary key, value text)")),
- Statement.of("CREATE TABLE bar (id bigint primary key, value text)"));
+ Statement.of("CREATE TABLE bar (id bigint primary key, value text)"),
+ Function.identity());
SpannerBatchUpdateException batchUpdateException =
assertThrows(
@@ -204,8 +207,8 @@ public void testHasDmlOrCopyStatementsAfter() {
spannerConnection,
mock(OptionsMetadata.class),
ImmutableList.of());
- onlyDmlStatements.execute(parsedUpdateStatement, updateStatement);
- onlyDmlStatements.execute(parsedUpdateStatement, updateStatement);
+ onlyDmlStatements.execute(parsedUpdateStatement, updateStatement, Function.identity());
+ onlyDmlStatements.execute(parsedUpdateStatement, updateStatement, Function.identity());
assertTrue(onlyDmlStatements.hasDmlOrCopyStatementsAfter(0));
assertTrue(onlyDmlStatements.hasDmlOrCopyStatementsAfter(1));
@@ -226,7 +229,7 @@ public void testHasDmlOrCopyStatementsAfter() {
spannerConnection,
mock(OptionsMetadata.class),
ImmutableList.of());
- dmlAndCopyStatements.execute(parsedUpdateStatement, updateStatement);
+ dmlAndCopyStatements.execute(parsedUpdateStatement, updateStatement, Function.identity());
dmlAndCopyStatements.executeCopy(
parsedCopyStatement, copyStatement, receiver, writer, executor);
assertTrue(dmlAndCopyStatements.hasDmlOrCopyStatementsAfter(0));
@@ -238,8 +241,8 @@ public void testHasDmlOrCopyStatementsAfter() {
spannerConnection,
mock(OptionsMetadata.class),
ImmutableList.of());
- onlySelectStatements.execute(parsedSelectStatement, selectStatement);
- onlySelectStatements.execute(parsedSelectStatement, selectStatement);
+ onlySelectStatements.execute(parsedSelectStatement, selectStatement, Function.identity());
+ onlySelectStatements.execute(parsedSelectStatement, selectStatement, Function.identity());
assertFalse(onlySelectStatements.hasDmlOrCopyStatementsAfter(0));
assertFalse(onlySelectStatements.hasDmlOrCopyStatementsAfter(1));
@@ -249,8 +252,10 @@ public void testHasDmlOrCopyStatementsAfter() {
spannerConnection,
mock(OptionsMetadata.class),
ImmutableList.of());
- onlyClientSideStatements.execute(parsedClientSideStatement, clientSideStatement);
- onlyClientSideStatements.execute(parsedClientSideStatement, clientSideStatement);
+ onlyClientSideStatements.execute(
+ parsedClientSideStatement, clientSideStatement, Function.identity());
+ onlyClientSideStatements.execute(
+ parsedClientSideStatement, clientSideStatement, Function.identity());
assertFalse(onlyClientSideStatements.hasDmlOrCopyStatementsAfter(0));
assertFalse(onlyClientSideStatements.hasDmlOrCopyStatementsAfter(1));
@@ -260,8 +265,8 @@ public void testHasDmlOrCopyStatementsAfter() {
spannerConnection,
mock(OptionsMetadata.class),
ImmutableList.of());
- onlyUnknownStatements.execute(parsedUnknownStatement, unknownStatement);
- onlyUnknownStatements.execute(parsedUnknownStatement, unknownStatement);
+ onlyUnknownStatements.execute(parsedUnknownStatement, unknownStatement, Function.identity());
+ onlyUnknownStatements.execute(parsedUnknownStatement, unknownStatement, Function.identity());
assertFalse(onlyUnknownStatements.hasDmlOrCopyStatementsAfter(0));
assertFalse(onlyUnknownStatements.hasDmlOrCopyStatementsAfter(1));
@@ -271,8 +276,8 @@ public void testHasDmlOrCopyStatementsAfter() {
spannerConnection,
mock(OptionsMetadata.class),
ImmutableList.of());
- dmlAndSelectStatements.execute(parsedUpdateStatement, updateStatement);
- dmlAndSelectStatements.execute(parsedSelectStatement, selectStatement);
+ dmlAndSelectStatements.execute(parsedUpdateStatement, updateStatement, Function.identity());
+ dmlAndSelectStatements.execute(parsedSelectStatement, selectStatement, Function.identity());
assertTrue(dmlAndSelectStatements.hasDmlOrCopyStatementsAfter(0));
assertFalse(dmlAndSelectStatements.hasDmlOrCopyStatementsAfter(1));
@@ -284,7 +289,7 @@ public void testHasDmlOrCopyStatementsAfter() {
ImmutableList.of());
copyAndSelectStatements.executeCopy(
parsedCopyStatement, copyStatement, receiver, writer, executor);
- copyAndSelectStatements.execute(parsedSelectStatement, selectStatement);
+ copyAndSelectStatements.execute(parsedSelectStatement, selectStatement, Function.identity());
assertTrue(copyAndSelectStatements.hasDmlOrCopyStatementsAfter(0));
assertFalse(copyAndSelectStatements.hasDmlOrCopyStatementsAfter(1));
@@ -296,7 +301,7 @@ public void testHasDmlOrCopyStatementsAfter() {
ImmutableList.of());
copyAndUnknownStatements.executeCopy(
parsedCopyStatement, copyStatement, receiver, writer, executor);
- copyAndUnknownStatements.execute(parsedUnknownStatement, unknownStatement);
+ copyAndUnknownStatements.execute(parsedUnknownStatement, unknownStatement, Function.identity());
assertTrue(copyAndUnknownStatements.hasDmlOrCopyStatementsAfter(0));
assertFalse(copyAndUnknownStatements.hasDmlOrCopyStatementsAfter(1));
}
@@ -321,7 +326,9 @@ public void testExecuteLocalStatement() throws ExecutionException, InterruptedEx
DatabaseId.of("p", "i", "d"), connection, mock(OptionsMetadata.class), localStatements);
Future resultFuture =
backendConnection.execute(
- parsedListDatabasesStatement, Statement.of(ListDatabasesStatement.LIST_DATABASES_SQL));
+ parsedListDatabasesStatement,
+ Statement.of(ListDatabasesStatement.LIST_DATABASES_SQL),
+ Function.identity());
backendConnection.flush();
verify(listDatabasesStatement).execute(backendConnection);
@@ -349,7 +356,8 @@ public void testExecuteOtherStatementWithLocalStatements()
BackendConnection backendConnection =
new BackendConnection(
DatabaseId.of("p", "i", "d"), connection, mock(OptionsMetadata.class), localStatements);
- Future resultFuture = backendConnection.execute(parsedStatement, statement);
+ Future resultFuture =
+ backendConnection.execute(parsedStatement, statement, Function.identity());
backendConnection.flush();
verify(listDatabasesStatement, never()).execute(backendConnection);
@@ -382,7 +390,8 @@ public void testGeneralException() {
connection,
mock(OptionsMetadata.class),
EMPTY_LOCAL_STATEMENTS);
- Future resultFuture = backendConnection.execute(parsedStatement, statement);
+ Future resultFuture =
+ backendConnection.execute(parsedStatement, statement, Function.identity());
backendConnection.flush();
ExecutionException executionException =
@@ -405,7 +414,8 @@ public void testCancelledException() {
connection,
mock(OptionsMetadata.class),
EMPTY_LOCAL_STATEMENTS);
- Future resultFuture = backendConnection.execute(parsedStatement, statement);
+ Future resultFuture =
+ backendConnection.execute(parsedStatement, statement, Function.identity());
backendConnection.flush();
ExecutionException executionException =
@@ -442,8 +452,9 @@ public void testDdlExceptionInBatch() {
connection,
mock(OptionsMetadata.class),
EMPTY_LOCAL_STATEMENTS);
- Future resultFuture1 = backendConnection.execute(parsedStatement1, statement1);
- backendConnection.execute(parsedStatement2, statement2);
+ Future resultFuture1 =
+ backendConnection.execute(parsedStatement1, statement1, Function.identity());
+ backendConnection.execute(parsedStatement2, statement2, Function.identity());
backendConnection.flush();
// The error will be set on the first statement in the batch, as the error occurs before
@@ -466,7 +477,7 @@ public void testReplacePgCatalogTables() {
new BackendConnection(
DatabaseId.of("p", "i", "d"), connection, options, EMPTY_LOCAL_STATEMENTS);
- backendConnection.execute(parsedStatement, statement);
+ backendConnection.execute(parsedStatement, statement, Function.identity());
backendConnection.flush();
verify(connection)
@@ -509,7 +520,7 @@ public void testDisableReplacePgCatalogTables() {
new BackendConnection(
DatabaseId.of("p", "i", "d"), connection, options, EMPTY_LOCAL_STATEMENTS);
- backendConnection.execute(parsedStatement, statement);
+ backendConnection.execute(parsedStatement, statement, Function.identity());
backendConnection.flush();
verify(connection).execute(statement);
@@ -530,7 +541,7 @@ public void testDoNotStartTransactionInBatch() {
mock(OptionsMetadata.class),
EMPTY_LOCAL_STATEMENTS);
- backendConnection.execute(parsedStatement, statement);
+ backendConnection.execute(parsedStatement, statement, Function.identity());
backendConnection.flush();
verify(connection).execute(statement);
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatementTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatementTest.java
index d637d1b596..3cf44c766b 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatementTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatementTest.java
@@ -14,15 +14,10 @@
package com.google.cloud.spanner.pgadapter.statements;
-import static com.google.cloud.spanner.pgadapter.statements.IntermediatePreparedStatement.extractParameters;
-import static com.google.cloud.spanner.pgadapter.statements.IntermediatePreparedStatement.transformDeleteToSelectParams;
-import static com.google.cloud.spanner.pgadapter.statements.IntermediatePreparedStatement.transformInsertToSelectParams;
-import static com.google.cloud.spanner.pgadapter.statements.IntermediatePreparedStatement.transformUpdateToSelectParams;
import static com.google.cloud.spanner.pgadapter.statements.SimpleParserTest.splitStatements;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
@@ -160,166 +155,6 @@ public void testUpdateResultCount_NoResult() {
assertEquals(ResultType.NO_RESULT, statement.getStatementResult().getResultType());
}
- @Test
- public void testTransformInsertValuesToSelectParams() {
- assertEquals(
- "select $1, $2 from (select col1=$1, col2=$2 from foo) p",
- transformInsert("insert into foo (col1, col2) values ($1, $2)").getSql());
- assertEquals(
- "select $1, $2 from (select col1=$1, col2=$2 from foo) p",
- transformInsert("insert into foo(col1, col2) values ($1, $2)").getSql());
- assertEquals(
- "select $1, $2 from (select col1=$1, col2=$2 from foo) p",
- transformInsert("insert into foo (col1, col2) values($1, $2)").getSql());
- assertEquals(
- "select $1, $2 from (select col1=$1, col2=$2 from foo) p",
- transformInsert("insert into foo(col1, col2) values($1, $2)").getSql());
- assertEquals(
- "select $1, $2 from (select col1=$1, col2=$2 from foo) p",
- transformInsert("insert foo(col1, col2) values($1, $2)").getSql());
- assertEquals(
- "select $1, $2 from (select col1=$1, col2=$2 from foo) p",
- transformInsert("insert foo (col1, col2) values ($1, $2)").getSql());
- assertEquals(
- "select $1, $2, $3, $4 from (select col1=$1, col2=$2, col1=$3, col2=$4 from foo) p",
- transformInsert("insert into foo (col1, col2) values ($1, $2), ($3, $4)").getSql());
- assertEquals(
- "select $1, $2 from (select col1=$1::varchar, col2=$2::bigint from foo) p",
- transformInsert("insert into foo (col1, col2) values ($1::varchar, $2::bigint)").getSql());
- assertEquals(
- "select $1, $2, $3, $4 from (select col1=($1 + $2), col2=$3 || to_char($4) from foo) p",
- transformInsert("insert into foo (col1, col2) values (($1 + $2), $3 || to_char($4))")
- .getSql());
- assertEquals(
- "select $1, $2, $3, $4 from (select col1=($1 + $2), col2=$3 || to_char($4) from foo) p",
- transformInsert("insert into foo (col1, col2) values (($1 + $2), $3 || to_char($4))")
- .getSql());
- assertEquals(
- "select $1, $2, $3, $4, $5 from (select col1=$1 + $2 + 5, col2=$3 || to_char($4) || coalesce($5, '') from foo) p",
- transformInsert(
- "insert\ninto\nfoo\n(col1,\ncol2 ) values ($1 + $2 + 5, $3 || to_char($4) || coalesce($5, ''))")
- .getSql());
- assertEquals(
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 from "
- + "(select col1=$1, col2=$2, col3=$3, col4=$4, col5=$5, col6=$6, col7=$7, col8=$8, col9=$9, col10=$10 from foo) p",
- transformInsert(
- "insert into foo (col1, col2, col3, col4, col5, col6, col7, col8, col9, col10) "
- + "values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)")
- .getSql());
- assertEquals(
- "select $1, $2 from (select col1=$1, col2=$2 from \"foo\") p",
- transformInsert("insert\"foo\"(col1, col2)values($1, $2)").getSql());
- }
-
- @Test
- public void testTransformInsertSelectToSelectParams() {
- assertEquals(
- "select $1 from (select * from bar where some_col=$1) p",
- transformInsert("insert into foo select * from bar where some_col=$1").getSql());
- assertEquals(
- "select $1 from ((select * from bar where some_col=$1)) p",
- transformInsert("insert into foo (select * from bar where some_col=$1)").getSql());
- assertEquals(
- "select $1 from ((select * from(select col1, col2 from bar) where col2=$1)) p",
- transformInsert("insert into foo (select * from(select col1, col2 from bar) where col2=$1)")
- .getSql());
- assertEquals(
- "select $1 from (select * from bar where some_col=$1) p",
- transformInsert("insert foo select * from bar where some_col=$1").getSql());
- assertEquals(
- "select $1 from (select * from bar where some_col=$1) p",
- transformInsert("insert into foo (col1, col2) select * from bar where some_col=$1")
- .getSql());
- assertEquals(
- "select $1 from (select * from bar where some_col=$1) p",
- transformInsert("insert foo (col1, col2, col3) select * from bar where some_col=$1")
- .getSql());
- assertEquals(
- "select $1, $2 from (select * from bar where some_col=$1 limit $2) p",
- transformInsert(
- "insert foo (col1, col2, col3) select * from bar where some_col=$1 limit $2")
- .getSql());
- assertNull(transformInsert("insert into foo (col1 values ('test')"));
- }
-
- @Test
- public void testTransformUpdateToSelectParams() {
- assertEquals(
- "select $1, $2, $3 from (select col1=$1, col2=$2 from foo where id=$3) p",
- transformUpdate("update foo set col1=$1, col2=$2 where id=$3").getSql());
- assertEquals(
- "select $1, $2, $3 from (select col1=col2 + $1, "
- + "col2=coalesce($1, $2, $3, to_char(current_timestamp())), "
- + "col3 = 15 "
- + "from foo where id=$3 and value>100) p",
- transformUpdate(
- "update foo set col1=col2 + $1 , "
- + "col2=coalesce($1, $2, $3, to_char(current_timestamp())), "
- + "col3 = 15 "
- + "where id=$3 and value>100")
- .getSql());
- assertEquals(
- "select $1 from (select col1=$1 from foo) p",
- transformUpdate("update foo set col1=$1").getSql());
-
- assertNull(transformUpdate("update foo col1=1"));
- assertNull(transformUpdate("update foo col1=1 hwere col1=2"));
- assertNull(transformUpdate("udpate foo col1=1 where col1=2"));
-
- assertEquals(
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 from (select col1=$1, col2=$2, col3=$3, col4=$4, col5=$5, col6=$6, col7=$7, col8=$8, col9=$9 from foo where id=$10) p",
- transformUpdate(
- "update foo set col1=$1, col2=$2, col3=$3, col4=$4, col5=$5, col6=$6, col7=$7, col8=$8, col9=$9 where id=$10")
- .getSql());
- assertEquals(
- "select $1, $2, $3 from (select col1=(select col2 from bar where col3=$1), col2=$2 from foo where id=$3) p",
- transformUpdate(
- "update foo set col1=(select col2 from bar where col3=$1), col2=$2 where id=$3")
- .getSql());
- }
-
- @Test
- public void testTransformDeleteToSelectParams() {
- assertEquals(
- "select $1 from (select 1 from foo where id=$1) p",
- transformDelete("delete from foo where id=$1").getSql());
- assertEquals(
- "select $1, $2 from (select 1 from foo where id=$1 and bar > $2) p",
- transformDelete("delete foo\nwhere id=$1 and bar > $2").getSql());
- assertEquals(
- "select $1, $2, $3, $4, $5, $6, $7, $8, $9, $10 "
- + "from (select 1 from all_types "
- + "where col_bigint=$1 "
- + "and col_bool=$2 "
- + "and col_bytea=$3 "
- + "and col_float8=$4 "
- + "and col_int=$5 "
- + "and col_numeric=$6 "
- + "and col_timestamptz=$7 "
- + "and col_date=$8 "
- + "and col_varchar=$9 "
- + "and col_jsonb=$10"
- + ") p",
- transformDelete(
- "delete "
- + "from all_types "
- + "where col_bigint=$1 "
- + "and col_bool=$2 "
- + "and col_bytea=$3 "
- + "and col_float8=$4 "
- + "and col_int=$5 "
- + "and col_numeric=$6 "
- + "and col_timestamptz=$7 "
- + "and col_date=$8 "
- + "and col_varchar=$9 "
- + "and col_jsonb=$10")
- .getSql());
-
- assertNull(transformDelete("delete from foo"));
- assertNull(transformDelete("dlete from foo where id=$1"));
- assertNull(transformDelete("delete from foo hwere col1=2"));
- }
-
@Test
public void testInterruptedWhileWaitingForResult() throws Exception {
when(connectionHandler.getSpannerConnection()).thenReturn(connection);
@@ -337,16 +172,4 @@ public void testInterruptedWhileWaitingForResult() throws Exception {
PGException pgException = statement.getException();
assertEquals(SQLState.QueryCanceled, pgException.getSQLState());
}
-
- private static Statement transformInsert(String sql) {
- return transformInsertToSelectParams(mock(Connection.class), sql, extractParameters(sql));
- }
-
- private static Statement transformUpdate(String sql) {
- return transformUpdateToSelectParams(sql, extractParameters(sql));
- }
-
- private static Statement transformDelete(String sql) {
- return transformDeleteToSelectParams(sql, extractParameters(sql));
- }
}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java
index 79c5f1e689..3684b42b3e 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java
@@ -14,6 +14,8 @@
package com.google.cloud.spanner.pgadapter.statements;
+import static com.google.cloud.spanner.pgadapter.statements.IntermediatePortalStatement.NO_PARAMS;
+import static com.google.cloud.spanner.pgadapter.statements.IntermediatePreparedStatement.NO_PARAMETER_TYPES;
import static com.google.cloud.spanner.pgadapter.utils.ClientAutoDetector.EMPTY_LOCAL_STATEMENTS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -21,6 +23,8 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -65,7 +69,6 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@@ -121,14 +124,19 @@ public void testBasicSelectStatement() throws Exception {
when(connectionHandler.getConnectionMetadata()).thenReturn(connectionMetadata);
IntermediatePortalStatement intermediateStatement =
new IntermediatePortalStatement(
- connectionHandler, options, "", parse(sql), Statement.of(sql));
+ "",
+ new IntermediatePreparedStatement(
+ connectionHandler, options, "", NO_PARAMETER_TYPES, parse(sql), Statement.of(sql)),
+ NO_PARAMS,
+ ImmutableList.of(),
+ ImmutableList.of());
assertFalse(intermediateStatement.isExecuted());
assertEquals("SELECT", intermediateStatement.getCommand());
intermediateStatement.executeAsync(backendConnection);
- verify(backendConnection).execute(parse(sql), Statement.of(sql));
+ verify(backendConnection).execute(eq(parse(sql)), eq(Statement.of(sql)), any());
assertTrue(intermediateStatement.containsResultSet());
assertTrue(intermediateStatement.isExecuted());
assertEquals(StatementType.QUERY, intermediateStatement.getStatementType());
@@ -145,14 +153,19 @@ public void testBasicUpdateStatement() throws Exception {
when(connectionHandler.getConnectionMetadata()).thenReturn(connectionMetadata);
IntermediatePortalStatement intermediateStatement =
new IntermediatePortalStatement(
- connectionHandler, options, "", parse(sql), Statement.of(sql));
+ "",
+ new IntermediatePreparedStatement(
+ connectionHandler, options, "", NO_PARAMETER_TYPES, parse(sql), Statement.of(sql)),
+ NO_PARAMS,
+ ImmutableList.of(),
+ ImmutableList.of());
assertFalse(intermediateStatement.isExecuted());
assertEquals("UPDATE", intermediateStatement.getCommand());
intermediateStatement.executeAsync(backendConnection);
- verify(backendConnection).execute(parse(sql), Statement.of(sql));
+ verify(backendConnection).execute(eq(parse(sql)), eq(Statement.of(sql)), any());
assertFalse(intermediateStatement.containsResultSet());
assertTrue(intermediateStatement.isExecuted());
assertEquals(StatementType.UPDATE, intermediateStatement.getStatementType());
@@ -175,7 +188,12 @@ public void testBasicZeroUpdateCountResultStatement() throws Exception {
when(connection.execute(Statement.of(sql))).thenReturn(statementResult);
IntermediatePortalStatement intermediateStatement =
new IntermediatePortalStatement(
- connectionHandler, options, "", parse(sql), Statement.of(sql));
+ "",
+ new IntermediatePreparedStatement(
+ connectionHandler, options, "", NO_PARAMETER_TYPES, parse(sql), Statement.of(sql)),
+ NO_PARAMS,
+ ImmutableList.of(),
+ ImmutableList.of());
BackendConnection backendConnection =
new BackendConnection(
connectionHandler.getDatabaseId(), connection, options, EMPTY_LOCAL_STATEMENTS);
@@ -208,14 +226,19 @@ public void testBasicNoResultStatement() throws Exception {
when(connectionHandler.getConnectionMetadata()).thenReturn(connectionMetadata);
IntermediatePortalStatement intermediateStatement =
new IntermediatePortalStatement(
- connectionHandler, options, "", parse(sql), Statement.of(sql));
+ "",
+ new IntermediatePreparedStatement(
+ connectionHandler, options, "", NO_PARAMETER_TYPES, parse(sql), Statement.of(sql)),
+ NO_PARAMS,
+ ImmutableList.of(),
+ ImmutableList.of());
assertFalse(intermediateStatement.isExecuted());
assertEquals("CREATE", intermediateStatement.getCommand());
intermediateStatement.executeAsync(backendConnection);
- verify(backendConnection).execute(parse(sql), Statement.of(sql));
+ verify(backendConnection).execute(eq(parse(sql)), eq(Statement.of(sql)), any());
assertFalse(intermediateStatement.containsResultSet());
assertEquals(0, intermediateStatement.getUpdateCount());
assertTrue(intermediateStatement.isExecuted());
@@ -251,7 +274,12 @@ public void testBasicStatementExceptionGetsSetOnExceptedExecution() {
when(connectionHandler.getConnectionMetadata()).thenReturn(connectionMetadata);
IntermediatePortalStatement intermediateStatement =
new IntermediatePortalStatement(
- connectionHandler, options, "", parse(sql), Statement.of(sql));
+ "",
+ new IntermediatePreparedStatement(
+ connectionHandler, options, "", NO_PARAMETER_TYPES, parse(sql), Statement.of(sql)),
+ NO_PARAMS,
+ ImmutableList.of(),
+ ImmutableList.of());
BackendConnection backendConnection =
new BackendConnection(
connectionHandler.getDatabaseId(), connection, options, EMPTY_LOCAL_STATEMENTS);
@@ -294,23 +322,28 @@ public void testPreparedStatement() {
IntermediatePreparedStatement intermediateStatement =
new IntermediatePreparedStatement(
- connectionHandler, options, "", parse(sqlStatement), Statement.of(sqlStatement));
- intermediateStatement.setParameterDataTypes(parameterDataTypes);
+ connectionHandler,
+ options,
+ "",
+ parameterDataTypes,
+ parse(sqlStatement),
+ Statement.of(sqlStatement));
assertEquals(sqlStatement, intermediateStatement.getSql());
byte[][] parameters = {"userName".getBytes(), "20".getBytes(), "30".getBytes()};
IntermediatePortalStatement intermediatePortalStatement =
- intermediateStatement.bind(
+ intermediateStatement.createPortal(
"", parameters, Arrays.asList((short) 0, (short) 0, (short) 0), new ArrayList<>());
- intermediateStatement.executeAsync(backendConnection);
+ intermediatePortalStatement.bind(Statement.of(sqlStatement));
+ intermediatePortalStatement.executeAsync(backendConnection);
backendConnection.flush();
verify(connection).execute(statement);
assertEquals(sqlStatement, intermediatePortalStatement.getSql());
assertEquals("SELECT", intermediatePortalStatement.getCommand());
- assertFalse(intermediatePortalStatement.isExecuted());
+ assertTrue(intermediatePortalStatement.isExecuted());
assertTrue(intermediateStatement.isBound());
}
@@ -331,14 +364,19 @@ public void testPreparedStatementIllegalTypeThrowsException() {
IntermediatePreparedStatement intermediateStatement =
new IntermediatePreparedStatement(
- connectionHandler, options, "", parse(sqlStatement), Statement.of(sqlStatement));
- intermediateStatement.setParameterDataTypes(parameterDataTypes);
+ connectionHandler,
+ options,
+ "",
+ parameterDataTypes,
+ parse(sqlStatement),
+ Statement.of(sqlStatement));
byte[][] parameters = {"{}".getBytes()};
+ IntermediatePortalStatement portalStatement =
+ intermediateStatement.createPortal("", parameters, new ArrayList<>(), new ArrayList<>());
assertThrows(
- IllegalArgumentException.class,
- () -> intermediateStatement.bind("", parameters, new ArrayList<>(), new ArrayList<>()));
+ IllegalArgumentException.class, () -> portalStatement.bind(Statement.of(sqlStatement)));
}
@Test
@@ -349,62 +387,20 @@ public void testPreparedStatementDescribeDoesNotThrowException() {
when(connection.analyzeQuery(Statement.of(sqlStatement), QueryAnalyzeMode.PLAN))
.thenReturn(resultSet);
- IntermediatePreparedStatement intermediateStatement =
- new IntermediatePreparedStatement(
- connectionHandler, options, "", parse(sqlStatement), Statement.of(sqlStatement));
int[] parameters = new int[3];
Arrays.fill(parameters, Oid.INT8);
- intermediateStatement.setParameterDataTypes(parameters);
+ IntermediatePreparedStatement intermediateStatement =
+ new IntermediatePreparedStatement(
+ connectionHandler,
+ options,
+ "",
+ parameters,
+ parse(sqlStatement),
+ Statement.of(sqlStatement));
intermediateStatement.describe();
}
- @Test
- public void testPortalStatement() {
- when(connectionHandler.getSpannerConnection()).thenReturn(connection);
- when(connectionHandler.getConnectionMetadata()).thenReturn(connectionMetadata);
- String sqlStatement = "SELECT * FROM users WHERE age > $1 AND age < $2 AND name = $3";
-
- IntermediatePortalStatement intermediateStatement =
- new IntermediatePortalStatement(
- connectionHandler, options, "", parse(sqlStatement), Statement.of(sqlStatement));
- BackendConnection backendConnection =
- new BackendConnection(
- connectionHandler.getDatabaseId(), connection, options, EMPTY_LOCAL_STATEMENTS);
-
- intermediateStatement.describeAsync(backendConnection);
- backendConnection.flush();
-
- verify(connection).execute(Statement.of(sqlStatement));
-
- assertEquals(0, intermediateStatement.getParameterFormatCode(0));
- assertEquals(0, intermediateStatement.getParameterFormatCode(1));
- assertEquals(0, intermediateStatement.getParameterFormatCode(2));
- assertEquals(0, intermediateStatement.getResultFormatCode(0));
- assertEquals(0, intermediateStatement.getResultFormatCode(1));
- assertEquals(0, intermediateStatement.getResultFormatCode(2));
-
- intermediateStatement.setParameterFormatCodes(Collections.singletonList((short) 1));
- intermediateStatement.setResultFormatCodes(Collections.singletonList((short) 1));
-
- assertEquals(1, intermediateStatement.getParameterFormatCode(0));
- assertEquals(1, intermediateStatement.getParameterFormatCode(1));
- assertEquals(1, intermediateStatement.getParameterFormatCode(2));
- assertEquals(1, intermediateStatement.getResultFormatCode(0));
- assertEquals(1, intermediateStatement.getResultFormatCode(1));
- assertEquals(1, intermediateStatement.getResultFormatCode(2));
-
- intermediateStatement.setParameterFormatCodes(Arrays.asList((short) 0, (short) 1, (short) 0));
- intermediateStatement.setResultFormatCodes(Arrays.asList((short) 0, (short) 1, (short) 0));
-
- assertEquals(0, intermediateStatement.getParameterFormatCode(0));
- assertEquals(1, intermediateStatement.getParameterFormatCode(1));
- assertEquals(0, intermediateStatement.getParameterFormatCode(2));
- assertEquals(0, intermediateStatement.getResultFormatCode(0));
- assertEquals(1, intermediateStatement.getResultFormatCode(1));
- assertEquals(0, intermediateStatement.getResultFormatCode(2));
- }
-
@Test
public void testPortalStatementDescribePropagatesFailure() {
when(connectionHandler.getSpannerConnection()).thenReturn(connection);
@@ -413,7 +409,17 @@ public void testPortalStatementDescribePropagatesFailure() {
IntermediatePortalStatement intermediateStatement =
new IntermediatePortalStatement(
- connectionHandler, options, "", parse(sqlStatement), Statement.of(sqlStatement));
+ "",
+ new IntermediatePreparedStatement(
+ connectionHandler,
+ options,
+ "",
+ NO_PARAMETER_TYPES,
+ parse(sqlStatement),
+ Statement.of(sqlStatement)),
+ NO_PARAMS,
+ ImmutableList.of(),
+ ImmutableList.of());
BackendConnection backendConnection =
new BackendConnection(
connectionHandler.getDatabaseId(), connection, options, EMPTY_LOCAL_STATEMENTS);
@@ -538,7 +544,12 @@ public void testGetStatementResultBeforeFlushFails() {
when(connectionHandler.getConnectionMetadata()).thenReturn(connectionMetadata);
IntermediatePortalStatement intermediateStatement =
new IntermediatePortalStatement(
- connectionHandler, options, "", parse(sql), Statement.of(sql));
+ "",
+ new IntermediatePreparedStatement(
+ connectionHandler, options, "", NO_PARAMETER_TYPES, parse(sql), Statement.of(sql)),
+ NO_PARAMS,
+ ImmutableList.of(),
+ ImmutableList.of());
BackendConnection backendConnection =
new BackendConnection(
connectionHandler.getDatabaseId(), connection, options, EMPTY_LOCAL_STATEMENTS);
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/wireprotocol/ProtocolTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/wireprotocol/ProtocolTest.java
index 46257c1e82..43cc6e714e 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/wireprotocol/ProtocolTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/wireprotocol/ProtocolTest.java
@@ -258,7 +258,7 @@ public void testParseMessageException() throws Exception {
assertEquals(expectedSQL, ((ParseMessage) message).getStatement().getSql());
assertArrayEquals(
expectedParameterDataTypes,
- ((ParseMessage) message).getStatement().getParameterDataTypes());
+ ((ParseMessage) message).getStatement().getGivenParameterDataTypes());
when(connectionHandler.hasStatement(anyString())).thenReturn(false);
message.send();
@@ -322,7 +322,7 @@ public void testParseMessage() throws Exception {
assertEquals(expectedSQL, ((ParseMessage) message).getStatement().getSql());
assertArrayEquals(
expectedParameterDataTypes,
- ((ParseMessage) message).getStatement().getParameterDataTypes());
+ ((ParseMessage) message).getStatement().getGivenParameterDataTypes());
when(connectionHandler.hasStatement(anyString())).thenReturn(false);
message.send();
@@ -387,7 +387,7 @@ public void testParseMessageAcceptsUntypedParameter() throws Exception {
assertEquals(expectedSQL, ((ParseMessage) message).getStatement().getSql());
assertArrayEquals(
expectedParameterDataTypes,
- ((ParseMessage) message).getStatement().getParameterDataTypes());
+ ((ParseMessage) message).getStatement().getGivenParameterDataTypes());
when(connectionHandler.hasStatement(anyString())).thenReturn(false);
message.send();
@@ -437,7 +437,7 @@ public void testParseMessageWithNonMatchingParameterTypeCount() throws Exception
assertEquals(expectedSQL, ((ParseMessage) message).getStatement().getSql());
assertArrayEquals(
expectedParameterDataTypes,
- ((ParseMessage) message).getStatement().getParameterDataTypes());
+ ((ParseMessage) message).getStatement().getGivenParameterDataTypes());
when(connectionHandler.hasStatement(anyString())).thenReturn(false);
message.send();
@@ -625,7 +625,11 @@ public void testBindMessage() throws Exception {
resultCodesCount);
when(connectionHandler.getStatement(anyString())).thenReturn(intermediatePreparedStatement);
- when(intermediatePreparedStatement.getSql()).thenReturn("select * from foo");
+ when(intermediatePreparedStatement.createPortal(anyString(), any(), any(), any()))
+ .thenReturn(intermediatePortalStatement);
+ when(intermediatePortalStatement.getSql()).thenReturn("select * from foo");
+ when(intermediatePortalStatement.getPreparedStatement())
+ .thenReturn(intermediatePreparedStatement);
byte[][] expectedParameters = {parameter};
List expectedFormatCodes = new ArrayList<>();
@@ -652,13 +656,6 @@ public void testBindMessage() throws Exception {
assertEquals("select * from foo", ((BindMessage) message).getSql());
assertTrue(((BindMessage) message).hasParameterValues());
- when(intermediatePreparedStatement.bind(
- ArgumentMatchers.anyString(),
- ArgumentMatchers.any(),
- ArgumentMatchers.any(),
- ArgumentMatchers.any()))
- .thenReturn(intermediatePortalStatement);
-
message.send();
((BindMessage) message).flush();
verify(connectionHandler).registerPortal(expectedPortalName, intermediatePortalStatement);
@@ -729,6 +726,9 @@ public void testBindMessageOneNonTextParam() throws Exception {
when(connectionHandler.getConnectionMetadata()).thenReturn(connectionMetadata);
when(connectionMetadata.getInputStream()).thenReturn(inputStream);
when(connectionMetadata.getOutputStream()).thenReturn(outputStream);
+ when(connectionHandler.getStatement(anyString())).thenReturn(intermediatePreparedStatement);
+ when(intermediatePreparedStatement.createPortal(anyString(), any(), any(), any()))
+ .thenReturn(intermediatePortalStatement);
WireMessage message = ControlMessage.create(connectionHandler);
assertEquals(BindMessage.class, message.getClass());
@@ -799,6 +799,9 @@ public void testBindMessageAllNonTextParam() throws Exception {
when(connectionHandler.getConnectionMetadata()).thenReturn(connectionMetadata);
when(connectionMetadata.getInputStream()).thenReturn(inputStream);
when(connectionMetadata.getOutputStream()).thenReturn(outputStream);
+ when(connectionHandler.getStatement(anyString())).thenReturn(intermediatePreparedStatement);
+ when(intermediatePreparedStatement.createPortal(anyString(), any(), any(), any()))
+ .thenReturn(intermediatePortalStatement);
WireMessage message = ControlMessage.create(connectionHandler);
assertEquals(BindMessage.class, message.getClass());
@@ -885,6 +888,39 @@ public void testDescribeStatementMessage() throws Exception {
verify(messageSpy).handleDescribeStatement();
}
+ @Test
+ public void testDescribeMessageWithException() throws Exception {
+ byte[] messageMetadata = {'D'};
+ byte[] statementType = {'S'};
+ String statementName = "some statement\0";
+
+ byte[] length = intToBytes(4 + 1 + statementName.length());
+
+ byte[] value = Bytes.concat(messageMetadata, length, statementType, statementName.getBytes());
+
+ DataInputStream inputStream = new DataInputStream(new ByteArrayInputStream(value));
+ ByteArrayOutputStream result = new ByteArrayOutputStream();
+ DataOutputStream outputStream = new DataOutputStream(result);
+
+ when(connectionHandler.getStatement(anyString())).thenReturn(intermediatePreparedStatement);
+ when(connectionHandler.getConnectionMetadata()).thenReturn(connectionMetadata);
+ when(connectionMetadata.getInputStream()).thenReturn(inputStream);
+ when(connectionMetadata.getOutputStream()).thenReturn(outputStream);
+ when(connectionHandler.getExtendedQueryProtocolHandler())
+ .thenReturn(extendedQueryProtocolHandler);
+ when(intermediatePreparedStatement.hasException()).thenReturn(true);
+ when(intermediatePreparedStatement.getException())
+ .thenReturn(PGExceptionFactory.newPGException("test error", SQLState.InternalError));
+
+ WireMessage message = ControlMessage.create(connectionHandler);
+ assertEquals(DescribeMessage.class, message.getClass());
+ DescribeMessage describeMessage = (DescribeMessage) message;
+
+ PGException exception =
+ assertThrows(PGException.class, describeMessage::handleDescribeStatement);
+ assertEquals("test error", exception.getMessage());
+ }
+
@Test
public void testExecuteMessage() throws Exception {
byte[] messageMetadata = {'E'};
diff --git a/src/test/nodejs/typeorm/data-test/src/index.ts b/src/test/nodejs/typeorm/data-test/src/index.ts
index 3938e29b2e..b5c0b21cba 100644
--- a/src/test/nodejs/typeorm/data-test/src/index.ts
+++ b/src/test/nodejs/typeorm/data-test/src/index.ts
@@ -103,7 +103,7 @@ async function testCreateAllTypes(dataSource: DataSource) {
const allTypes = {
col_bigint: 2,
col_bool: true,
- col_bytea: Buffer.from(Buffer.from('some random string').toString('base64')),
+ col_bytea: Buffer.from('some random string'),
col_float8: 0.123456789,
col_int: 123456789,
col_numeric: 234.54235,
From 84244c78531d6b6cadc32bd255f824b9b2b20ed0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?=
Date: Fri, 25 Nov 2022 18:18:28 +0100
Subject: [PATCH 23/39] cleanup: remove 'guess types' feature (#497)
* refactor: use analyze to get statement params
* feat: use analyze to describe statements
* fix: get param index from param name instead of index in list
* test: add more tests
* test: add more tests + disable clirr
Disabling clirr as it only adds noise, and somehow refuses to pick up
the latest ignore.
* chore: cleanup
* fix: update test for analyzeUpdate
* fix: disable flaky assertion
* fix: remove accidentally committed folder
* fix: remove more assertions pending node-postgres 8.9
* fix: disable more assertions awaiting 8.9
* cleanup: remove 'guess types' feature
Guessing the type of a parameter is no longer needed with backend
parameter type inferring support.
Fixes #487
* test: add new test to keep coverage over current value
---
.gitignore | 1 +
.../spanner/pgadapter/parsers/Parser.java | 30 +-------------
.../pgadapter/session/SessionState.java | 40 -------------------
.../spanner/pgadapter/JdbcMockServerTest.java | 32 ++++++++++++++-
.../cloud/spanner/pgadapter/ServerTest.java | 34 ++++++++++++++++
.../pgadapter/session/SessionStateTest.java | 25 ------------
.../pgadapter/statements/StatementTest.java | 3 --
7 files changed, 66 insertions(+), 99 deletions(-)
diff --git a/.gitignore b/.gitignore
index d0ffb7224e..17e8e3a670 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@ target/
*.lst
output.txt
__pycache__
+.DS_Store
src/test/golang/**/*.h
src/test/golang/**/*.so
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/parsers/Parser.java b/src/main/java/com/google/cloud/spanner/pgadapter/parsers/Parser.java
index b26ca6a58a..1490cc3b19 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/parsers/Parser.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/parsers/Parser.java
@@ -27,7 +27,6 @@
import com.google.cloud.spanner.pgadapter.session.SessionState;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
-import java.util.Set;
import org.postgresql.core.Oid;
/**
@@ -55,27 +54,6 @@ public static FormatCode of(short code) {
protected static final Charset UTF8 = StandardCharsets.UTF_8;
protected T item;
- /**
- * Guess the type of a parameter with unspecified type.
- *
- * @param item The value to guess the type for
- * @param formatCode The encoding that is used for the value
- * @return The {@link Oid} type code that is guessed for the value or {@link Oid#UNSPECIFIED} if
- * no type could be guessed.
- */
- private static int guessType(Set guessTypes, byte[] item, FormatCode formatCode) {
- if (formatCode == FormatCode.TEXT && item != null) {
- String value = new String(item, StandardCharsets.UTF_8);
- if (guessTypes.contains(Oid.TIMESTAMPTZ) && TimestampParser.isTimestamp(value)) {
- return Oid.TIMESTAMPTZ;
- }
- if (guessTypes.contains(Oid.DATE) && DateParser.isDate(value)) {
- return Oid.DATE;
- }
- }
- return Oid.UNSPECIFIED;
- }
-
/**
* Factory method to create a Parser subtype with a designated type from a byte array.
*
@@ -117,13 +95,7 @@ public static Parser> create(
case Oid.JSONB:
return new JsonbParser(item, formatCode);
case Oid.UNSPECIFIED:
- // Try to guess the type based on the value. Use an unspecified parser if no type could be
- // determined.
- int type = guessType(sessionState.getGuessTypes(), item, formatCode);
- if (type == Oid.UNSPECIFIED) {
- return new UnspecifiedParser(item, formatCode);
- }
- return create(sessionState, item, type, formatCode);
+ return new UnspecifiedParser(item, formatCode);
default:
throw new IllegalArgumentException("Unsupported parameter type: " + oidType);
}
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/session/SessionState.java b/src/main/java/com/google/cloud/spanner/pgadapter/session/SessionState.java
index 2af43c4d52..1c05bb33d2 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/session/SessionState.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/session/SessionState.java
@@ -16,7 +16,6 @@
import static com.google.cloud.spanner.pgadapter.session.CopySettings.initCopySettings;
-import com.google.api.client.util.Strings;
import com.google.api.core.InternalApi;
import com.google.cloud.spanner.ErrorCode;
import com.google.cloud.spanner.SpannerException;
@@ -30,7 +29,6 @@
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSet.Builder;
import com.google.common.collect.Sets;
import java.time.ZoneId;
import java.util.ArrayList;
@@ -45,7 +43,6 @@
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
-import javax.annotation.Nonnull;
/** {@link SessionState} contains all session variables for a connection. */
@InternalApi
@@ -422,43 +419,6 @@ private ZoneId zoneIdFromString(String value) {
}
}
- /**
- * Returns a set of OIDs that PGAdapter should try to guess if it receives an untyped parameter
- * value. This is needed because some clients (JDBC) deliberately send parameters without a type
- * code to force the server to infer the type. This specifically applies to date/timestamp
- * parameters.
- */
- public Set getGuessTypes() {
- PGSetting setting = internalGet(toKey("spanner", "guess_types"), false);
- if (setting == null || Strings.isNullOrEmpty(setting.getSetting())) {
- return ImmutableSet.of();
- }
- return convertOidListToSet(setting.getSetting());
- }
-
- /** Keep a cache of 1 element ready as the setting is not likely to change often. */
- private final Map> cachedGuessTypes = new HashMap<>(1);
-
- Set convertOidListToSet(@Nonnull String value) {
- if (cachedGuessTypes.containsKey(value)) {
- return cachedGuessTypes.get(value);
- }
-
- Builder builder = ImmutableSet.builder();
- String[] oids = value.split(",");
- for (String oid : oids) {
- try {
- builder.add(Integer.valueOf(oid));
- } catch (Exception ignore) {
- // ignore invalid oids.
- }
- }
- cachedGuessTypes.clear();
- cachedGuessTypes.put(value, builder.build());
-
- return cachedGuessTypes.get(value);
- }
-
@SafeVarargs
static T tryGetFirstNonNull(T defaultResult, Callable... callables) {
T value;
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
index e9f1d85e3a..60f6fc25a5 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
@@ -518,7 +518,21 @@ public void testQueryWithParameters() throws SQLException {
public void testQueryWithLegacyDateParameter() throws SQLException {
String jdbcSql = "select col_date from all_types where col_date=?";
String pgSql = "select col_date from all_types where col_date=$1";
- mockSpanner.putStatementResult(StatementResult.query(Statement.of(pgSql), ALL_TYPES_RESULTSET));
+ ResultSetMetadata metadata =
+ ALL_TYPES_METADATA
+ .toBuilder()
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.DATE).build())
+ .build())
+ .build())
+ .build();
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of(pgSql), ALL_TYPES_RESULTSET.toBuilder().setMetadata(metadata).build()));
mockSpanner.putStatementResult(
StatementResult.query(
Statement.newBuilder(pgSql).bind("p1").to(Date.parseDate("2022-03-29")).build(),
@@ -572,7 +586,21 @@ public void testQueryWithLegacyDateParameter() throws SQLException {
public void testAutoDescribedStatementsAreReused() throws SQLException {
String jdbcSql = "select col_date from all_types where col_date=?";
String pgSql = "select col_date from all_types where col_date=$1";
- mockSpanner.putStatementResult(StatementResult.query(Statement.of(pgSql), ALL_TYPES_RESULTSET));
+ ResultSetMetadata metadata =
+ ALL_TYPES_METADATA
+ .toBuilder()
+ .setUndeclaredParameters(
+ StructType.newBuilder()
+ .addFields(
+ Field.newBuilder()
+ .setName("p1")
+ .setType(Type.newBuilder().setCode(TypeCode.DATE).build())
+ .build())
+ .build())
+ .build();
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of(pgSql), ALL_TYPES_RESULTSET.toBuilder().setMetadata(metadata).build()));
mockSpanner.putStatementResult(
StatementResult.query(
Statement.newBuilder(pgSql).bind("p1").to(Date.parseDate("2022-03-29")).build(),
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/ServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/ServerTest.java
index 584ac56a25..9604872cac 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/ServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/ServerTest.java
@@ -94,4 +94,38 @@ public void testMainWithInvalidParam() {
System.setErr(originalErr);
}
}
+
+ @Test
+ public void testInvalidKeyStore() {
+ ByteArrayOutputStream outArrayStream = new ByteArrayOutputStream();
+ PrintStream out = new PrintStream(outArrayStream);
+ ByteArrayOutputStream errArrayStream = new ByteArrayOutputStream();
+ PrintStream err = new PrintStream(errArrayStream);
+
+ PrintStream originalOut = System.out;
+ PrintStream originalErr = System.err;
+ String originalKeyStore = System.getProperty("javax.net.ssl.keyStore");
+ System.setOut(out);
+ System.setErr(err);
+ System.setProperty("javax.net.ssl.keyStore", "/path/to/non/existing/file.pfx");
+
+ try {
+ Server.main(new String[] {});
+ assertEquals(
+ "The server could not be started because an error occurred: Key store /path/to/non/existing/file.pfx does not exist\n",
+ errArrayStream.toString());
+ assertTrue(
+ outArrayStream.toString(),
+ outArrayStream
+ .toString()
+ .startsWith(
+ String.format("-- Starting PGAdapter version %s --", Server.getVersion())));
+ } finally {
+ System.setOut(originalOut);
+ System.setErr(originalErr);
+ if (originalKeyStore != null) {
+ System.setProperty("javax.net.ssl.keyStore", originalKeyStore);
+ }
+ }
+ }
}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/session/SessionStateTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/session/SessionStateTest.java
index 4a48dee646..29bdd30608 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/session/SessionStateTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/session/SessionStateTest.java
@@ -33,14 +33,12 @@
import com.google.cloud.spanner.pgadapter.metadata.OptionsMetadata.DdlTransactionMode;
import com.google.cloud.spanner.pgadapter.statements.PgCatalog;
import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.postgresql.core.Oid;
@RunWith(JUnit4.class)
public class SessionStateTest {
@@ -828,29 +826,6 @@ public void testDdlTransactionMode_bootVal() {
assertEquals(DdlTransactionMode.Single, state.getDdlTransactionMode());
}
- @Test
- public void testGuessTypes_defaultNonJdbc() {
- OptionsMetadata optionsMetadata = mock(OptionsMetadata.class);
- SessionState state = new SessionState(ImmutableMap.of(), optionsMetadata);
- assertEquals(ImmutableSet.of(), state.getGuessTypes());
- }
-
- @Test
- public void testGuessTypes_defaultJdbc() {
- OptionsMetadata optionsMetadata = mock(OptionsMetadata.class);
- SessionState state = new SessionState(ImmutableMap.of(), optionsMetadata);
- state.set("spanner", "guess_types", String.format("%d,%d", Oid.TIMESTAMPTZ, Oid.DATE));
- assertEquals(ImmutableSet.of(Oid.TIMESTAMPTZ, Oid.DATE), state.getGuessTypes());
- }
-
- @Test
- public void testGuessTypes_invalidOids() {
- OptionsMetadata optionsMetadata = mock(OptionsMetadata.class);
- SessionState state = new SessionState(ImmutableMap.of(), optionsMetadata);
- state.set("spanner", "guess_types", String.format("%d,%d,foo", Oid.TIMESTAMPTZ, Oid.DATE));
- assertEquals(ImmutableSet.of(Oid.TIMESTAMPTZ, Oid.DATE), state.getGuessTypes());
- }
-
@Test
public void testGetDefaultTimeZone() {
Map originalSettings = ImmutableMap.copyOf(SessionState.SERVER_SETTINGS);
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java
index 3684b42b3e..a0e666cd73 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/statements/StatementTest.java
@@ -58,7 +58,6 @@
import com.google.cloud.spanner.pgadapter.wireprotocol.QueryMessage;
import com.google.cloud.spanner.pgadapter.wireprotocol.WireMessage;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Bytes;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
@@ -303,7 +302,6 @@ public void testPreparedStatement() {
when(extendedQueryProtocolHandler.getBackendConnection()).thenReturn(backendConnection);
SessionState sessionState = mock(SessionState.class);
when(backendConnection.getSessionState()).thenReturn(sessionState);
- when(sessionState.getGuessTypes()).thenReturn(ImmutableSet.of());
String sqlStatement = "SELECT * FROM users WHERE age > $2 AND age < $3 AND name = $1";
int[] parameterDataTypes = new int[] {Oid.VARCHAR, Oid.INT8, Oid.INT4};
@@ -357,7 +355,6 @@ public void testPreparedStatementIllegalTypeThrowsException() {
when(extendedQueryProtocolHandler.getBackendConnection()).thenReturn(backendConnection);
SessionState sessionState = mock(SessionState.class);
when(backendConnection.getSessionState()).thenReturn(sessionState);
- when(sessionState.getGuessTypes()).thenReturn(ImmutableSet.of());
String sqlStatement = "SELECT * FROM users WHERE metadata = $1";
int[] parameterDataTypes = new int[] {Oid.JSON};
From c1d7e4eff240449245f223bc17793f393cafea2f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?=
Date: Fri, 25 Nov 2022 18:32:47 +0100
Subject: [PATCH 24/39] feat: support DML RETURNING clause (#498)
* refactor: use analyze to get statement params
* feat: use analyze to describe statements
* fix: get param index from param name instead of index in list
* test: add more tests
* test: add more tests + disable clirr
Disabling clirr as it only adds noise, and somehow refuses to pick up
the latest ignore.
* chore: cleanup
* fix: update test for analyzeUpdate
* fix: disable flaky assertion
* fix: remove accidentally committed folder
* fix: remove more assertions pending node-postgres 8.9
* fix: disable more assertions awaiting 8.9
* cleanup: remove 'guess types' feature
Guessing the type of a parameter is no longer needed with backend
parameter type inferring support.
Fixes #487
* feat: support DML RETURNING clause
Add support for DML statements with a RETURNING clause.
* chore: remove unnecessary change
---
.../metadata/SendResultSetState.java | 4 +-
.../statements/BackendConnection.java | 2 +-
.../statements/IntermediateStatement.java | 3 +-
.../wireprotocol/ControlMessage.java | 33 +--
src/test/golang/pgadapter_pgx_tests/pgx.go | 89 ++++++++
.../spanner/pgadapter/JdbcMockServerTest.java | 199 ++++++++++++++++++
.../pgadapter/golang/PgxMockServerTest.java | 92 ++++++++
.../spanner/pgadapter/golang/PgxTest.java | 2 +
8 files changed, 406 insertions(+), 18 deletions(-)
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/metadata/SendResultSetState.java b/src/main/java/com/google/cloud/spanner/pgadapter/metadata/SendResultSetState.java
index b5ef457089..ad796c5186 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/metadata/SendResultSetState.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/metadata/SendResultSetState.java
@@ -31,7 +31,9 @@ public SendResultSetState(String commandTag, long numRowsSent, boolean hasMoreRo
}
public String getCommandAndNumRows() {
- return getCommandTag() + " " + getNumberOfRowsSent();
+ String command = getCommandTag();
+ command += ("INSERT".equals(command) ? " 0 " : " ") + getNumberOfRowsSent();
+ return command;
}
public String getCommandTag() {
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/BackendConnection.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/BackendConnection.java
index 665105e986..3dd971594e 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/BackendConnection.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/BackendConnection.java
@@ -277,7 +277,7 @@ StatementResult bindAndExecute(Statement statement) {
statement = statementBinder.apply(statement);
if (analyze) {
ResultSet resultSet;
- if (parsedStatement.isUpdate()) {
+ if (parsedStatement.isUpdate() && !parsedStatement.hasReturningClause()) {
// TODO(#477): Single analyzeUpdate statements that are executed in an implicit
// transaction could use a single-use read/write transaction. Replays are not
// dangerous for those.
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatement.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatement.java
index 73bdfcf985..1cacc63e25 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatement.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatement.java
@@ -137,7 +137,8 @@ public void close() throws Exception {
/** @return True if this is a select statement, false otherwise. */
public boolean containsResultSet() {
- return this.parsedStatement.isQuery();
+ return this.parsedStatement.isQuery()
+ || (this.parsedStatement.isUpdate() && this.parsedStatement.hasReturningClause());
}
/** @return True if this statement was executed, False otherwise. */
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ControlMessage.java b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ControlMessage.java
index 0a3b07a08a..f75639cded 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ControlMessage.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/wireprotocol/ControlMessage.java
@@ -28,6 +28,7 @@
import com.google.cloud.spanner.connection.Connection;
import com.google.cloud.spanner.connection.ConnectionOptionsHelper;
import com.google.cloud.spanner.connection.StatementResult;
+import com.google.cloud.spanner.connection.StatementResult.ResultType;
import com.google.cloud.spanner.pgadapter.ConnectionHandler;
import com.google.cloud.spanner.pgadapter.ConnectionHandler.ConnectionStatus;
import com.google.cloud.spanner.pgadapter.ConnectionHandler.QueryMode;
@@ -253,7 +254,6 @@ public void sendSpannerResult(IntermediateStatement statement, QueryMode mode, l
if (statement.getStatementResult() == null) {
return;
}
-
switch (statement.getStatementType()) {
case DDL:
case CLIENT_SIDE:
@@ -261,23 +261,26 @@ public void sendSpannerResult(IntermediateStatement statement, QueryMode mode, l
new CommandCompleteResponse(this.outputStream, command).send(false);
break;
case QUERY:
- SendResultSetState state = sendResultSet(statement, mode, maxRows);
- statement.setHasMoreData(state.hasMoreRows());
- if (state.hasMoreRows()) {
- new PortalSuspendedResponse(this.outputStream).send(false);
+ case UPDATE:
+ if (statement.getStatementResult().getResultType() == ResultType.RESULT_SET) {
+ SendResultSetState state = sendResultSet(statement, mode, maxRows);
+ statement.setHasMoreData(state.hasMoreRows());
+ if (state.hasMoreRows()) {
+ new PortalSuspendedResponse(this.outputStream).send(false);
+ } else {
+ statement.close();
+ new CommandCompleteResponse(this.outputStream, state.getCommandAndNumRows())
+ .send(false);
+ }
} else {
- statement.close();
- new CommandCompleteResponse(this.outputStream, state.getCommandAndNumRows()).send(false);
+ // For an INSERT command, the tag is INSERT oid rows, where rows is the number of rows
+ // inserted. oid used to be the object ID of the inserted row if rows was 1 and the target
+ // table had OIDs, but OIDs system columns are not supported anymore; therefore oid is
+ // always 0.
+ command += ("INSERT".equals(command) ? " 0 " : " ") + statement.getUpdateCount();
+ new CommandCompleteResponse(this.outputStream, command).send(false);
}
break;
- case UPDATE:
- // For an INSERT command, the tag is INSERT oid rows, where rows is the number of rows
- // inserted. oid used to be the object ID of the inserted row if rows was 1 and the target
- // table had OIDs, but OIDs system columns are not supported anymore; therefore oid is
- // always 0.
- command += ("INSERT".equals(command) ? " 0 " : " ") + statement.getUpdateCount();
- new CommandCompleteResponse(this.outputStream, command).send(false);
- break;
default:
throw new IllegalStateException("Unknown statement type: " + statement.getStatement());
}
diff --git a/src/test/golang/pgadapter_pgx_tests/pgx.go b/src/test/golang/pgadapter_pgx_tests/pgx.go
index 48e56c6094..cee4fcd735 100644
--- a/src/test/golang/pgadapter_pgx_tests/pgx.go
+++ b/src/test/golang/pgadapter_pgx_tests/pgx.go
@@ -254,6 +254,95 @@ func TestInsertNullsAllDataTypes(connString string) *C.char {
return nil
}
+//export TestInsertAllDataTypesReturning
+func TestInsertAllDataTypesReturning(connString string) *C.char {
+ ctx := context.Background()
+ conn, err := pgx.Connect(ctx, connString)
+ if err != nil {
+ return C.CString(err.Error())
+ }
+ defer conn.Close(ctx)
+
+ sql := "INSERT INTO all_types (col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar, col_jsonb) " +
+ "values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning *"
+ numeric := pgtype.Numeric{}
+ _ = numeric.Set("6.626")
+ timestamptz, _ := time.Parse(time.RFC3339Nano, "2022-03-24T07:39:10.123456789+01:00")
+ date := pgtype.Date{}
+ _ = date.Set("2022-04-02")
+ var row pgx.Row
+ if strings.Contains(connString, "prefer_simple_protocol=true") {
+ // Simple mode will format the date as '2022-04-02 00:00:00Z', which is not supported by the
+ // backend yet.
+ row = conn.QueryRow(ctx, sql, 100, true, []byte("test_bytes"), 3.14, 1, numeric, timestamptz, "2022-04-02", "test_string", "{\"key\": \"value\"}")
+ } else {
+ row = conn.QueryRow(ctx, sql, 100, true, []byte("test_bytes"), 3.14, 1, numeric, timestamptz, date, "test_string", "{\"key\": \"value\"}")
+ }
+ var bigintValue int64
+ var boolValue bool
+ var byteaValue []byte
+ var float8Value float64
+ var intValue int
+ var numericValue pgtype.Numeric // pgx by default maps numeric to string
+ var timestamptzValue time.Time
+ var dateValue time.Time
+ var varcharValue string
+ var jsonbValue string
+
+ err = row.Scan(
+ &bigintValue,
+ &boolValue,
+ &byteaValue,
+ &float8Value,
+ &intValue,
+ &numericValue,
+ ×tamptzValue,
+ &dateValue,
+ &varcharValue,
+ &jsonbValue,
+ )
+ if err != nil {
+ return C.CString(fmt.Sprintf("Failed to execute insert: %v", err.Error()))
+ }
+ if g, w := bigintValue, int64(1); g != w {
+ return C.CString(fmt.Sprintf("value mismatch\n Got: %v\nWant: %v", g, w))
+ }
+ if g, w := boolValue, true; g != w {
+ return C.CString(fmt.Sprintf("value mismatch\n Got: %v\nWant: %v", g, w))
+ }
+ if g, w := byteaValue, []byte("test"); !reflect.DeepEqual(g, w) {
+ return C.CString(fmt.Sprintf("value mismatch\n Got: %v\nWant: %v", g, w))
+ }
+ if g, w := float8Value, 3.14; g != w {
+ return C.CString(fmt.Sprintf("value mismatch\n Got: %v\nWant: %v", g, w))
+ }
+ if g, w := intValue, 100; g != w {
+ return C.CString(fmt.Sprintf("value mismatch\n Got: %v\nWant: %v", g, w))
+ }
+ var wantNumericValue pgtype.Numeric
+ _ = wantNumericValue.Scan("6.626")
+ if g, w := numericValue, wantNumericValue; !reflect.DeepEqual(g, w) {
+ return C.CString(fmt.Sprintf("value mismatch\n Got: %v\nWant: %v", g, w))
+ }
+ wantDateValue, _ := time.Parse("2006-01-02", "2022-03-29")
+ if g, w := dateValue, wantDateValue; !reflect.DeepEqual(g, w) {
+ return C.CString(fmt.Sprintf("value mismatch\n Got: %v\nWant: %v", g, w))
+ }
+ // Encoding the timestamp values as a parameter will truncate it to microsecond precision.
+ wantTimestamptzValue, _ := time.Parse(time.RFC3339Nano, "2022-02-16T13:18:02.123456+00:00")
+ if g, w := timestamptzValue.UTC().String(), wantTimestamptzValue.UTC().String(); g != w {
+ return C.CString(fmt.Sprintf("value mismatch\n Got: %v\nWant: %v", g, w))
+ }
+ if g, w := varcharValue, "test"; g != w {
+ return C.CString(fmt.Sprintf("value mismatch\n Got: %v\nWant: %v", g, w))
+ }
+ if g, w := jsonbValue, "{\"key\": \"value\"}"; g != w {
+ return C.CString(fmt.Sprintf("value mismatch\n Got: %v\nWant: %v", g, w))
+ }
+
+ return nil
+}
+
//export TestUpdateAllDataTypes
func TestUpdateAllDataTypes(connString string) *C.char {
ctx := context.Background()
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
index 60f6fc25a5..0676b60cb5 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java
@@ -42,6 +42,7 @@
import com.google.cloud.spanner.pgadapter.wireprotocol.ExecuteMessage;
import com.google.cloud.spanner.pgadapter.wireprotocol.ParseMessage;
import com.google.common.collect.ImmutableList;
+import com.google.protobuf.ListValue;
import com.google.protobuf.Value;
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlRequest;
import com.google.spanner.v1.BeginTransactionRequest;
@@ -66,6 +67,7 @@
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.time.LocalDate;
@@ -1620,6 +1622,123 @@ public void testPreparedStatement() throws SQLException {
}
}
+ @Test
+ public void testPreparedStatementReturning() throws SQLException {
+ String pgSql =
+ "insert into all_types "
+ + "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar, col_jsonb) "
+ + "values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) "
+ + "returning *";
+ String sql =
+ "insert into all_types "
+ + "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar, col_jsonb) "
+ + "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "
+ + "returning *";
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of(pgSql),
+ com.google.spanner.v1.ResultSet.newBuilder()
+ .setMetadata(
+ ALL_TYPES_METADATA
+ .toBuilder()
+ .setUndeclaredParameters(
+ createParameterTypesMetadata(
+ ImmutableList.of(
+ TypeCode.INT64,
+ TypeCode.BOOL,
+ TypeCode.BYTES,
+ TypeCode.FLOAT64,
+ TypeCode.INT64,
+ TypeCode.NUMERIC,
+ TypeCode.TIMESTAMP,
+ TypeCode.DATE,
+ TypeCode.STRING,
+ TypeCode.JSON))
+ .getUndeclaredParameters())
+ .build())
+ .setStats(ResultSetStats.newBuilder().build())
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.newBuilder(pgSql)
+ .bind("p1")
+ .to(1L)
+ .bind("p2")
+ .to(true)
+ .bind("p3")
+ .to(ByteArray.copyFrom("test"))
+ .bind("p4")
+ .to(3.14d)
+ .bind("p5")
+ .to(100L)
+ .bind("p6")
+ .to(com.google.cloud.spanner.Value.pgNumeric("6.626"))
+ .bind("p7")
+ .to(Timestamp.parseTimestamp("2022-02-16T13:18:02.123457000Z"))
+ .bind("p8")
+ .to(Date.parseDate("2022-03-29"))
+ .bind("p9")
+ .to("test")
+ .bind("p10")
+ .to("{\"key\": \"value\"}")
+ .build(),
+ com.google.spanner.v1.ResultSet.newBuilder()
+ .setMetadata(ALL_TYPES_METADATA)
+ .setStats(ResultSetStats.newBuilder().setRowCountExact(1L).build())
+ .addRows(ALL_TYPES_RESULTSET.getRows(0))
+ .build()));
+
+ OffsetDateTime zonedDateTime =
+ LocalDateTime.of(2022, 2, 16, 13, 18, 2, 123456789).atOffset(ZoneOffset.UTC);
+ try (Connection connection = DriverManager.getConnection(createUrl())) {
+ try (PreparedStatement statement = connection.prepareStatement(sql)) {
+ ParameterMetaData parameterMetaData = statement.getParameterMetaData();
+ assertEquals(10, parameterMetaData.getParameterCount());
+ assertEquals(Types.BIGINT, parameterMetaData.getParameterType(1));
+ assertEquals(Types.BIT, parameterMetaData.getParameterType(2));
+ assertEquals(Types.BINARY, parameterMetaData.getParameterType(3));
+ assertEquals(Types.DOUBLE, parameterMetaData.getParameterType(4));
+ assertEquals(Types.BIGINT, parameterMetaData.getParameterType(5));
+ assertEquals(Types.NUMERIC, parameterMetaData.getParameterType(6));
+ assertEquals(Types.TIMESTAMP, parameterMetaData.getParameterType(7));
+ assertEquals(Types.DATE, parameterMetaData.getParameterType(8));
+ assertEquals(Types.VARCHAR, parameterMetaData.getParameterType(9));
+ // TODO: Enable when support for JSONB has been enabled.
+ // assertEquals(Types.OTHER, parameterMetaData.getParameterType(10));
+ ResultSetMetaData metadata = statement.getMetaData();
+ assertEquals(10, metadata.getColumnCount());
+ assertEquals(Types.BIGINT, metadata.getColumnType(1));
+ assertEquals(Types.BIT, metadata.getColumnType(2));
+ assertEquals(Types.BINARY, metadata.getColumnType(3));
+ assertEquals(Types.DOUBLE, metadata.getColumnType(4));
+ assertEquals(Types.BIGINT, metadata.getColumnType(5));
+ assertEquals(Types.NUMERIC, metadata.getColumnType(6));
+ assertEquals(Types.TIMESTAMP, metadata.getColumnType(7));
+ assertEquals(Types.DATE, metadata.getColumnType(8));
+ assertEquals(Types.VARCHAR, metadata.getColumnType(9));
+ // TODO: Enable when support for JSONB has been enabled.
+ // assertEquals(Types.OTHER, metadata.getColumnType(10));
+
+ int index = 0;
+ statement.setLong(++index, 1L);
+ statement.setBoolean(++index, true);
+ statement.setBytes(++index, "test".getBytes(StandardCharsets.UTF_8));
+ statement.setDouble(++index, 3.14d);
+ statement.setInt(++index, 100);
+ statement.setBigDecimal(++index, new BigDecimal("6.626"));
+ statement.setObject(++index, zonedDateTime);
+ statement.setObject(++index, LocalDate.of(2022, 3, 29));
+ statement.setString(++index, "test");
+ statement.setString(++index, "{\"key\": \"value\"}");
+
+ try (ResultSet resultSet = statement.executeQuery()) {
+ assertTrue(resultSet.next());
+ assertFalse(resultSet.next());
+ }
+ }
+ }
+ }
+
@Test
public void testCursorSuccess() throws SQLException {
try (Connection connection = DriverManager.getConnection(createUrl())) {
@@ -2939,6 +3058,86 @@ public void testDescribeStatementWithMoreThan50Parameters() throws SQLException
}
}
+ @Test
+ public void testDmlReturning() throws SQLException {
+ String sql = "INSERT INTO test (value) values ('test') RETURNING id";
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of(sql),
+ com.google.spanner.v1.ResultSet.newBuilder()
+ .setMetadata(
+ createMetadata(ImmutableList.of(TypeCode.INT64), ImmutableList.of("id")))
+ .setStats(ResultSetStats.newBuilder().setRowCountExact(1L).build())
+ .addRows(
+ ListValue.newBuilder()
+ .addValues(Value.newBuilder().setStringValue("9999").build())
+ .build())
+ .build()));
+
+ try (Connection connection = DriverManager.getConnection(createUrl())) {
+ try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) {
+ assertTrue(resultSet.next());
+ assertEquals(9999L, resultSet.getLong(1));
+ assertFalse(resultSet.next());
+ }
+ try (java.sql.Statement statement = connection.createStatement()) {
+ assertTrue(statement.execute(sql));
+ try (ResultSet resultSet = statement.getResultSet()) {
+ assertTrue(resultSet.next());
+ assertEquals(9999L, resultSet.getLong(1));
+ assertFalse(resultSet.next());
+ }
+ assertFalse(statement.getMoreResults());
+ assertEquals(-1, statement.getUpdateCount());
+ }
+ }
+ }
+
+ @Test
+ public void testDmlReturningMultipleRows() throws SQLException {
+ String sql = "UPDATE test SET value='new_value' WHERE value='old_value' RETURNING id";
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of(sql),
+ com.google.spanner.v1.ResultSet.newBuilder()
+ .setMetadata(
+ createMetadata(ImmutableList.of(TypeCode.INT64), ImmutableList.of("id")))
+ .setStats(ResultSetStats.newBuilder().setRowCountExact(3L).build())
+ .addRows(
+ ListValue.newBuilder()
+ .addValues(Value.newBuilder().setStringValue("1").build())
+ .build())
+ .addRows(
+ ListValue.newBuilder()
+ .addValues(Value.newBuilder().setStringValue("2").build())
+ .build())
+ .addRows(
+ ListValue.newBuilder()
+ .addValues(Value.newBuilder().setStringValue("3").build())
+ .build())
+ .build()));
+
+ try (Connection connection = DriverManager.getConnection(createUrl())) {
+ try (ResultSet resultSet = connection.createStatement().executeQuery(sql)) {
+ assertTrue(resultSet.next());
+ assertEquals(1L, resultSet.getLong(1));
+ assertTrue(resultSet.next());
+ assertEquals(2L, resultSet.getLong(1));
+ assertTrue(resultSet.next());
+ assertEquals(3L, resultSet.getLong(1));
+ assertFalse(resultSet.next());
+ }
+ }
+
+ assertEquals(1, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
+ ExecuteSqlRequest executeRequest =
+ mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0);
+ assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
+ assertTrue(executeRequest.getTransaction().hasBegin());
+ assertTrue(executeRequest.getTransaction().getBegin().hasReadWrite());
+ assertEquals(1, mockSpanner.countRequestsOfType(CommitRequest.class));
+ }
+
private void verifySettingIsNull(Connection connection, String setting) throws SQLException {
try (ResultSet resultSet =
connection.createStatement().executeQuery(String.format("show %s", setting))) {
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxMockServerTest.java
index bb3c157aff..410fd2075d 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxMockServerTest.java
@@ -401,6 +401,98 @@ public void testInsertAllDataTypes() {
assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
}
+ @Test
+ public void testInsertAllDataTypesReturning() {
+ String sql =
+ "INSERT INTO all_types "
+ + "(col_bigint, col_bool, col_bytea, col_float8, col_int, col_numeric, col_timestamptz, col_date, col_varchar, col_jsonb) "
+ + "values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning *";
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.of(sql),
+ ResultSet.newBuilder()
+ .setMetadata(
+ ALL_TYPES_METADATA
+ .toBuilder()
+ .setUndeclaredParameters(
+ createParameterTypesMetadata(
+ ImmutableList.of(
+ TypeCode.INT64,
+ TypeCode.BOOL,
+ TypeCode.BYTES,
+ TypeCode.FLOAT64,
+ TypeCode.INT64,
+ TypeCode.NUMERIC,
+ TypeCode.TIMESTAMP,
+ TypeCode.DATE,
+ TypeCode.STRING,
+ TypeCode.STRING))
+ .getUndeclaredParameters()))
+ .setStats(ResultSetStats.newBuilder().build())
+ .build()));
+ mockSpanner.putStatementResult(
+ StatementResult.query(
+ Statement.newBuilder(sql)
+ .bind("p1")
+ .to(100L)
+ .bind("p2")
+ .to(true)
+ .bind("p3")
+ .to(ByteArray.copyFrom("test_bytes"))
+ .bind("p4")
+ .to(3.14d)
+ .bind("p5")
+ .to(1L)
+ .bind("p6")
+ .to(com.google.cloud.spanner.Value.pgNumeric("6.626"))
+ .bind("p7")
+ .to(Timestamp.parseTimestamp("2022-03-24T06:39:10.123456000Z"))
+ .bind("p8")
+ .to(Date.parseDate("2022-04-02"))
+ .bind("p9")
+ .to("test_string")
+ .bind("p10")
+ .to("{\"key\": \"value\"}")
+ .build(),
+ ResultSet.newBuilder()
+ .setMetadata(
+ ALL_TYPES_METADATA
+ .toBuilder()
+ .setUndeclaredParameters(
+ createParameterTypesMetadata(
+ ImmutableList.of(
+ TypeCode.INT64,
+ TypeCode.BOOL,
+ TypeCode.BYTES,
+ TypeCode.FLOAT64,
+ TypeCode.INT64,
+ TypeCode.NUMERIC,
+ TypeCode.TIMESTAMP,
+ TypeCode.DATE,
+ TypeCode.STRING,
+ TypeCode.STRING))
+ .getUndeclaredParameters()))
+ .setStats(ResultSetStats.newBuilder().setRowCountExact(1L).build())
+ .addRows(ALL_TYPES_RESULTSET.getRows(0))
+ .build()));
+
+ String res = pgxTest.TestInsertAllDataTypesReturning(createConnString());
+
+ assertNull(res);
+ List requests = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class);
+ // pgx by default always uses prepared statements. That means that each request is sent two
+ // times to the backend the first time it is executed:
+ // 1. DescribeStatement (parameters)
+ // 2. Execute
+ assertEquals(2, requests.size());
+ ExecuteSqlRequest describeParamsRequest = requests.get(0);
+ assertEquals(sql, describeParamsRequest.getSql());
+ assertEquals(QueryMode.PLAN, describeParamsRequest.getQueryMode());
+ ExecuteSqlRequest executeRequest = requests.get(1);
+ assertEquals(sql, executeRequest.getSql());
+ assertEquals(QueryMode.NORMAL, executeRequest.getQueryMode());
+ }
+
@Test
public void testInsertBatch() {
String sql =
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxTest.java
index efd9f97acc..3521d6cdbe 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/golang/PgxTest.java
@@ -31,6 +31,8 @@ public interface PgxTest extends Library {
String TestInsertNullsAllDataTypes(GoString connString);
+ String TestInsertAllDataTypesReturning(GoString connString);
+
String TestUpdateAllDataTypes(GoString connString);
String TestPrepareStatement(GoString connString);
From 03f4d3785a6ea08a821d02529eac08d8f1472d1a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?=
Date: Fri, 25 Nov 2022 20:08:38 +0100
Subject: [PATCH 25/39] chore: add pg_class as CTE (#515)
---
.../pgadapter/statements/PgCatalog.java | 105 +++++++-
.../csharp/AbstractNpgsqlMockServerTest.java | 231 ++++++++++++++++++
2 files changed, 335 insertions(+), 1 deletion(-)
diff --git a/src/main/java/com/google/cloud/spanner/pgadapter/statements/PgCatalog.java b/src/main/java/com/google/cloud/spanner/pgadapter/statements/PgCatalog.java
index 2a96ef030b..7aa28a3d2e 100644
--- a/src/main/java/com/google/cloud/spanner/pgadapter/statements/PgCatalog.java
+++ b/src/main/java/com/google/cloud/spanner/pgadapter/statements/PgCatalog.java
@@ -27,6 +27,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.regex.Pattern;
@InternalApi
public class PgCatalog {
@@ -35,15 +36,22 @@ public class PgCatalog {
new TableOrIndexName("pg_catalog", "pg_namespace"),
new TableOrIndexName(null, "pg_namespace"),
new TableOrIndexName(null, "pg_namespace"), new TableOrIndexName(null, "pg_namespace"),
+ new TableOrIndexName("pg_catalog", "pg_class"), new TableOrIndexName(null, "pg_class"),
+ new TableOrIndexName(null, "pg_class"), new TableOrIndexName(null, "pg_class"),
new TableOrIndexName("pg_catalog", "pg_type"), new TableOrIndexName(null, "pg_type"),
new TableOrIndexName(null, "pg_type"), new TableOrIndexName(null, "pg_type"),
new TableOrIndexName("pg_catalog", "pg_settings"),
new TableOrIndexName(null, "pg_settings"),
new TableOrIndexName(null, "pg_settings"), new TableOrIndexName(null, "pg_settings"));
+ private static final ImmutableMap FUNCTION_REPLACEMENTS =
+ ImmutableMap.of(
+ Pattern.compile("pg_catalog.pg_table_is_visible\\(.+\\)"), "true",
+ Pattern.compile("pg_table_is_visible\\(.+\\)"), "true");
private final Map pgCatalogTables =
ImmutableMap.of(
new TableOrIndexName(null, "pg_namespace"), new PgNamespace(),
+ new TableOrIndexName(null, "pg_class"), new PgClass(),
new TableOrIndexName(null, "pg_type"), new PgType(),
new TableOrIndexName(null, "pg_settings"), new PgSettings());
private final SessionState sessionState;
@@ -70,9 +78,18 @@ public Statement replacePgCatalogTables(Statement statement) {
return addCommonTableExpressions(replacedTablesStatement.y(), cteBuilder.build());
}
+ static String replaceKnownUnsupportedFunctions(Statement statement) {
+ String sql = statement.getSql();
+ for (Entry functionReplacement : FUNCTION_REPLACEMENTS.entrySet()) {
+ sql = functionReplacement.getKey().matcher(sql).replaceAll(functionReplacement.getValue());
+ }
+ return sql;
+ }
+
static Statement addCommonTableExpressions(
Statement statement, ImmutableList tableExpressions) {
- SimpleParser parser = new SimpleParser(statement.getSql());
+ String sql = replaceKnownUnsupportedFunctions(statement);
+ SimpleParser parser = new SimpleParser(sql);
boolean hadCommonTableExpressions = parser.eatKeyword("with");
String tableExpressionsSql = String.join(",\n", tableExpressions);
Statement.Builder builder =
@@ -224,4 +241,90 @@ public String getTableExpression() {
return sessionState.generatePGSettingsCte();
}
}
+
+ private static class PgClass implements PgCatalogTable {
+ private static final String PG_CLASS_CTE =
+ "pg_class as (\n"
+ + " select\n"
+ + " -1 as oid,\n"
+ + " table_name as relname,\n"
+ + " case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
+ + " 0 as reltype,\n"
+ + " 0 as reloftype,\n"
+ + " 0 as relowner,\n"
+ + " 1 as relam,\n"
+ + " 0 as relfilenode,\n"
+ + " 0 as reltablespace,\n"
+ + " 0 as relpages,\n"
+ + " 0.0::float8 as reltuples,\n"
+ + " 0 as relallvisible,\n"
+ + " 0 as reltoastrelid,\n"
+ + " false as relhasindex,\n"
+ + " false as relisshared,\n"
+ + " 'p' as relpersistence,\n"
+ + " 'r' as relkind,\n"
+ + " count(*) as relnatts,\n"
+ + " 0 as relchecks,\n"
+ + " false as relhasrules,\n"
+ + " false as relhastriggers,\n"
+ + " false as relhassubclass,\n"
+ + " false as relrowsecurity,\n"
+ + " false as relforcerowsecurity,\n"
+ + " true as relispopulated,\n"
+ + " 'n' as relreplident,\n"
+ + " false as relispartition,\n"
+ + " 0 as relrewrite,\n"
+ + " 0 as relfrozenxid,\n"
+ + " 0 as relminmxid,\n"
+ + " '{}'::bigint[] as relacl,\n"
+ + " '{}'::text[] as reloptions,\n"
+ + " 0 as relpartbound\n"
+ + "from information_schema.tables t\n"
+ + "inner join information_schema.columns using (table_catalog, table_schema, table_name)\n"
+ + "group by t.table_name, t.table_schema\n"
+ + "union all\n"
+ + "select\n"
+ + " -1 as oid,\n"
+ + " i.index_name as relname,\n"
+ + " case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
+ + " 0 as reltype,\n"
+ + " 0 as reloftype,\n"
+ + " 0 as relowner,\n"
+ + " 1 as relam,\n"
+ + " 0 as relfilenode,\n"
+ + " 0 as reltablespace,\n"
+ + " 0 as relpages,\n"
+ + " 0.0::float8 as reltuples,\n"
+ + " 0 as relallvisible,\n"
+ + " 0 as reltoastrelid,\n"
+ + " false as relhasindex,\n"
+ + " false as relisshared,\n"
+ + " 'p' as relpersistence,\n"
+ + " 'r' as relkind,\n"
+ + " count(*) as relnatts,\n"
+ + " 0 as relchecks,\n"
+ + " false as relhasrules,\n"
+ + " false as relhastriggers,\n"
+ + " false as relhassubclass,\n"
+ + " false as relrowsecurity,\n"
+ + " false as relforcerowsecurity,\n"
+ + " true as relispopulated,\n"
+ + " 'n' as relreplident,\n"
+ + " false as relispartition,\n"
+ + " 0 as relrewrite,\n"
+ + " 0 as relfrozenxid,\n"
+ + " 0 as relminmxid,\n"
+ + " '{}'::bigint[] as relacl,\n"
+ + " '{}'::text[] as reloptions,\n"
+ + " 0 as relpartbound\n"
+ + "from information_schema.indexes i\n"
+ + "inner join information_schema.index_columns using (table_catalog, table_schema, table_name)\n"
+ + "group by i.index_name, i.table_schema\n"
+ + ")";
+
+ @Override
+ public String getTableExpression() {
+ return PG_CLASS_CTE;
+ }
+ }
}
diff --git a/src/test/java/com/google/cloud/spanner/pgadapter/csharp/AbstractNpgsqlMockServerTest.java b/src/test/java/com/google/cloud/spanner/pgadapter/csharp/AbstractNpgsqlMockServerTest.java
index e303a9f986..1f40ffd61f 100644
--- a/src/test/java/com/google/cloud/spanner/pgadapter/csharp/AbstractNpgsqlMockServerTest.java
+++ b/src/test/java/com/google/cloud/spanner/pgadapter/csharp/AbstractNpgsqlMockServerTest.java
@@ -81,6 +81,83 @@ public abstract class AbstractNpgsqlMockServerTest extends AbstractMockServerTes
+ " select 1184 as oid, 'timestamptz' as typname, (select oid from pg_namespace where nspname='pg_catalog') as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'D' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1185 as typarray, 'timestamptz_in' as typinput, 'timestamptz_out' as typoutput, 'timestamptz_recv' as typreceive, 'timestamptz_send' as typsend, 'timestamptztypmodin' as typmodin, 'timestamptztypmodout' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
+ " select 1700 as oid, 'numeric' as typname, (select oid from pg_namespace where nspname='pg_catalog') as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1231 as typarray, 'numeric_in' as typinput, 'numeric_out' as typoutput, 'numeric_recv' as typreceive, 'numeric_send' as typsend, 'numerictypmodin' as typmodin, 'numerictypmodout' as typmodout, '-' as typanalyze, 'i' as typalign, 'm' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
+ " select 3802 as oid, 'jsonb' as typname, (select oid from pg_namespace where nspname='pg_catalog') as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'U' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 3807 as typarray, 'jsonb_in' as typinput, 'jsonb_out' as typoutput, 'jsonb_recv' as typreceive, 'jsonb_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl\n"
+ + "),\n"
+ + "pg_class as (\n"
+ + " select\n"
+ + " -1 as oid,\n"
+ + " table_name as relname,\n"
+ + " case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
+ + " 0 as reltype,\n"
+ + " 0 as reloftype,\n"
+ + " 0 as relowner,\n"
+ + " 1 as relam,\n"
+ + " 0 as relfilenode,\n"
+ + " 0 as reltablespace,\n"
+ + " 0 as relpages,\n"
+ + " 0.0::float8 as reltuples,\n"
+ + " 0 as relallvisible,\n"
+ + " 0 as reltoastrelid,\n"
+ + " false as relhasindex,\n"
+ + " false as relisshared,\n"
+ + " 'p' as relpersistence,\n"
+ + " 'r' as relkind,\n"
+ + " count(*) as relnatts,\n"
+ + " 0 as relchecks,\n"
+ + " false as relhasrules,\n"
+ + " false as relhastriggers,\n"
+ + " false as relhassubclass,\n"
+ + " false as relrowsecurity,\n"
+ + " false as relforcerowsecurity,\n"
+ + " true as relispopulated,\n"
+ + " 'n' as relreplident,\n"
+ + " false as relispartition,\n"
+ + " 0 as relrewrite,\n"
+ + " 0 as relfrozenxid,\n"
+ + " 0 as relminmxid,\n"
+ + " '{}'::bigint[] as relacl,\n"
+ + " '{}'::text[] as reloptions,\n"
+ + " 0 as relpartbound\n"
+ + "from information_schema.tables t\n"
+ + "inner join information_schema.columns using (table_catalog, table_schema, table_name)\n"
+ + "group by t.table_name, t.table_schema\n"
+ + "union all\n"
+ + "select\n"
+ + " -1 as oid,\n"
+ + " i.index_name as relname,\n"
+ + " case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
+ + " 0 as reltype,\n"
+ + " 0 as reloftype,\n"
+ + " 0 as relowner,\n"
+ + " 1 as relam,\n"
+ + " 0 as relfilenode,\n"
+ + " 0 as reltablespace,\n"
+ + " 0 as relpages,\n"
+ + " 0.0::float8 as reltuples,\n"
+ + " 0 as relallvisible,\n"
+ + " 0 as reltoastrelid,\n"
+ + " false as relhasindex,\n"
+ + " false as relisshared,\n"
+ + " 'p' as relpersistence,\n"
+ + " 'r' as relkind,\n"
+ + " count(*) as relnatts,\n"
+ + " 0 as relchecks,\n"
+ + " false as relhasrules,\n"
+ + " false as relhastriggers,\n"
+ + " false as relhassubclass,\n"
+ + " false as relrowsecurity,\n"
+ + " false as relforcerowsecurity,\n"
+ + " true as relispopulated,\n"
+ + " 'n' as relreplident,\n"
+ + " false as relispartition,\n"
+ + " 0 as relrewrite,\n"
+ + " 0 as relfrozenxid,\n"
+ + " 0 as relminmxid,\n"
+ + " '{}'::bigint[] as relacl,\n"
+ + " '{}'::text[] as reloptions,\n"
+ + " 0 as relpartbound\n"
+ + "from information_schema.indexes i\n"
+ + "inner join information_schema.index_columns using (table_catalog, table_schema, table_name)\n"
+ + "group by i.index_name, i.table_schema\n"
+ ")\n"
+ "SELECT ns.nspname, t.oid, t.typname, t.typtype, t.typnotnull, t.elemtypoid\n"
+ "FROM (\n"
@@ -150,6 +227,83 @@ public abstract class AbstractNpgsqlMockServerTest extends AbstractMockServerTes
+ " select 1184 as oid, 'timestamptz' as typname, (select oid from pg_namespace where nspname='pg_catalog') as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'D' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1185 as typarray, 'timestamptz_in' as typinput, 'timestamptz_out' as typoutput, 'timestamptz_recv' as typreceive, 'timestamptz_send' as typsend, 'timestamptztypmodin' as typmodin, 'timestamptztypmodout' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
+ " select 1700 as oid, 'numeric' as typname, (select oid from pg_namespace where nspname='pg_catalog') as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1231 as typarray, 'numeric_in' as typinput, 'numeric_out' as typoutput, 'numeric_recv' as typreceive, 'numeric_send' as typsend, 'numerictypmodin' as typmodin, 'numerictypmodout' as typmodout, '-' as typanalyze, 'i' as typalign, 'm' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
+ " select 3802 as oid, 'jsonb' as typname, (select oid from pg_namespace where nspname='pg_catalog') as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'U' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 3807 as typarray, 'jsonb_in' as typinput, 'jsonb_out' as typoutput, 'jsonb_recv' as typreceive, 'jsonb_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl\n"
+ + "),\n"
+ + "pg_class as (\n"
+ + " select\n"
+ + " -1 as oid,\n"
+ + " table_name as relname,\n"
+ + " case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
+ + " 0 as reltype,\n"
+ + " 0 as reloftype,\n"
+ + " 0 as relowner,\n"
+ + " 1 as relam,\n"
+ + " 0 as relfilenode,\n"
+ + " 0 as reltablespace,\n"
+ + " 0 as relpages,\n"
+ + " 0.0::float8 as reltuples,\n"
+ + " 0 as relallvisible,\n"
+ + " 0 as reltoastrelid,\n"
+ + " false as relhasindex,\n"
+ + " false as relisshared,\n"
+ + " 'p' as relpersistence,\n"
+ + " 'r' as relkind,\n"
+ + " count(*) as relnatts,\n"
+ + " 0 as relchecks,\n"
+ + " false as relhasrules,\n"
+ + " false as relhastriggers,\n"
+ + " false as relhassubclass,\n"
+ + " false as relrowsecurity,\n"
+ + " false as relforcerowsecurity,\n"
+ + " true as relispopulated,\n"
+ + " 'n' as relreplident,\n"
+ + " false as relispartition,\n"
+ + " 0 as relrewrite,\n"
+ + " 0 as relfrozenxid,\n"
+ + " 0 as relminmxid,\n"
+ + " '{}'::bigint[] as relacl,\n"
+ + " '{}'::text[] as reloptions,\n"
+ + " 0 as relpartbound\n"
+ + "from information_schema.tables t\n"
+ + "inner join information_schema.columns using (table_catalog, table_schema, table_name)\n"
+ + "group by t.table_name, t.table_schema\n"
+ + "union all\n"
+ + "select\n"
+ + " -1 as oid,\n"
+ + " i.index_name as relname,\n"
+ + " case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
+ + " 0 as reltype,\n"
+ + " 0 as reloftype,\n"
+ + " 0 as relowner,\n"
+ + " 1 as relam,\n"
+ + " 0 as relfilenode,\n"
+ + " 0 as reltablespace,\n"
+ + " 0 as relpages,\n"
+ + " 0.0::float8 as reltuples,\n"
+ + " 0 as relallvisible,\n"
+ + " 0 as reltoastrelid,\n"
+ + " false as relhasindex,\n"
+ + " false as relisshared,\n"
+ + " 'p' as relpersistence,\n"
+ + " 'r' as relkind,\n"
+ + " count(*) as relnatts,\n"
+ + " 0 as relchecks,\n"
+ + " false as relhasrules,\n"
+ + " false as relhastriggers,\n"
+ + " false as relhassubclass,\n"
+ + " false as relrowsecurity,\n"
+ + " false as relforcerowsecurity,\n"
+ + " true as relispopulated,\n"
+ + " 'n' as relreplident,\n"
+ + " false as relispartition,\n"
+ + " 0 as relrewrite,\n"
+ + " 0 as relfrozenxid,\n"
+ + " 0 as relminmxid,\n"
+ + " '{}'::bigint[] as relacl,\n"
+ + " '{}'::text[] as reloptions,\n"
+ + " 0 as relpartbound\n"
+ + "from information_schema.indexes i\n"
+ + "inner join information_schema.index_columns using (table_catalog, table_schema, table_name)\n"
+ + "group by i.index_name, i.table_schema\n"
+ ")\n"
+ "SELECT ns.nspname, t.oid, t.typname, t.typtype, t.typnotnull, t.elemtypoid\n"
+ "FROM (\n"
@@ -393,6 +547,83 @@ public abstract class AbstractNpgsqlMockServerTest extends AbstractMockServerTes
+ " select 1184 as oid, 'timestamptz' as typname, (select oid from pg_namespace where nspname='pg_catalog') as typnamespace, null as typowner, 8 as typlen, true as typbyval, 'b' as typtype, 'D' as typcategory, true as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1185 as typarray, 'timestamptz_in' as typinput, 'timestamptz_out' as typoutput, 'timestamptz_recv' as typreceive, 'timestamptz_send' as typsend, 'timestamptztypmodin' as typmodin, 'timestamptztypmodout' as typmodout, '-' as typanalyze, 'd' as typalign, 'p' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
+ " select 1700 as oid, 'numeric' as typname, (select oid from pg_namespace where nspname='pg_catalog') as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'N' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 1231 as typarray, 'numeric_in' as typinput, 'numeric_out' as typoutput, 'numeric_recv' as typreceive, 'numeric_send' as typsend, 'numerictypmodin' as typmodin, 'numerictypmodout' as typmodout, '-' as typanalyze, 'i' as typalign, 'm' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl union all\n"
+ " select 3802 as oid, 'jsonb' as typname, (select oid from pg_namespace where nspname='pg_catalog') as typnamespace, null as typowner, -1 as typlen, false as typbyval, 'b' as typtype, 'U' as typcategory, false as typispreferred, true as typisdefined, ',' as typdelim, 0 as typrelid, 0 as typelem, 3807 as typarray, 'jsonb_in' as typinput, 'jsonb_out' as typoutput, 'jsonb_recv' as typreceive, 'jsonb_send' as typsend, '-' as typmodin, '-' as typmodout, '-' as typanalyze, 'i' as typalign, 'x' as typstorage, false as typnotnull, 0 as typbasetype, -1 as typtypmod, 0 as typndims, 0 as typcollation, null as typdefaultbin, null as typdefault, null as typacl\n"
+ + "),\n"
+ + "pg_class as (\n"
+ + " select\n"
+ + " -1 as oid,\n"
+ + " table_name as relname,\n"
+ + " case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
+ + " 0 as reltype,\n"
+ + " 0 as reloftype,\n"
+ + " 0 as relowner,\n"
+ + " 1 as relam,\n"
+ + " 0 as relfilenode,\n"
+ + " 0 as reltablespace,\n"
+ + " 0 as relpages,\n"
+ + " 0.0::float8 as reltuples,\n"
+ + " 0 as relallvisible,\n"
+ + " 0 as reltoastrelid,\n"
+ + " false as relhasindex,\n"
+ + " false as relisshared,\n"
+ + " 'p' as relpersistence,\n"
+ + " 'r' as relkind,\n"
+ + " count(*) as relnatts,\n"
+ + " 0 as relchecks,\n"
+ + " false as relhasrules,\n"
+ + " false as relhastriggers,\n"
+ + " false as relhassubclass,\n"
+ + " false as relrowsecurity,\n"
+ + " false as relforcerowsecurity,\n"
+ + " true as relispopulated,\n"
+ + " 'n' as relreplident,\n"
+ + " false as relispartition,\n"
+ + " 0 as relrewrite,\n"
+ + " 0 as relfrozenxid,\n"
+ + " 0 as relminmxid,\n"
+ + " '{}'::bigint[] as relacl,\n"
+ + " '{}'::text[] as reloptions,\n"
+ + " 0 as relpartbound\n"
+ + "from information_schema.tables t\n"
+ + "inner join information_schema.columns using (table_catalog, table_schema, table_name)\n"
+ + "group by t.table_name, t.table_schema\n"
+ + "union all\n"
+ + "select\n"
+ + " -1 as oid,\n"
+ + " i.index_name as relname,\n"
+ + " case table_schema when 'pg_catalog' then 11 when 'public' then 2200 else 0 end as relnamespace,\n"
+ + " 0 as reltype,\n"
+ + " 0 as reloftype,\n"
+ + " 0 as relowner,\n"
+ + " 1 as relam,\n"
+ + " 0 as relfilenode,\n"
+ + " 0 as reltablespace,\n"
+ + " 0 as relpages,\n"
+ + " 0.0::float8 as reltuples,\n"
+ + " 0 as relallvisible,\n"
+ + " 0 as reltoastrelid,\n"
+ + " false as relhasindex,\n"
+ + " false as relisshared,\n"
+ + " 'p' as relpersistence,\n"
+ + " 'r' as relkind,\n"
+ + " count(*) as relnatts,\n"
+ + " 0 as relchecks,\n"
+ + " false as relhasrules,\n"
+ + " false as relhastriggers,\n"
+ + " false as relhassubclass,\n"
+ + " false as relrowsecurity,\n"
+ + " false as relforcerowsecurity,\n"
+ + " true as relispopulated,\n"
+ + " 'n' as relreplident,\n"
+ + " false as relispartition,\n"
+ + " 0 as relrewrite,\n"
+ + " 0 as relfrozenxid,\n"
+ + " 0 as relminmxid,\n"
+ + " '{}'::bigint[] as relacl,\n"
+ + " '{}'::text[] as reloptions,\n"
+ + " 0 as relpartbound\n"
+ + "from information_schema.indexes i\n"
+ + "inner join information_schema.index_columns using (table_catalog, table_schema, table_name)\n"
+ + "group by i.index_name, i.table_schema\n"
+ ")\n"
+ "-- Load field definitions for (free-standing) composite types\n"
+ "SELECT typ.oid, att.attname, att.atttypid\n"
From 8f23a48cf72eca6d5acf2c64a02fc61751e858bb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?=
Date: Mon, 28 Nov 2022 13:41:42 +0100
Subject: [PATCH 26/39] cleanup: sample tests could fail silently and/or
randomly (#516)
Writing the properties files for sample applications did not always work,
as the changes were not explicitly flushed to disk. This sometimes caused
an empty properties file to be written to disk.
Also fixes an issue in the Hibernate sample test that would cause the test
to get stuck if an error occurred, as the Hibernate SessionFactory was not
closed in that case.
Fixes #461
---
samples/java/hibernate/pom.xml | 48 -----------
.../cloud/postgres/HibernateSampleTest.java | 80 ++++++++++---------
.../pgadapter/hibernate/ITHibernateTest.java | 18 +++--
.../pgadapter/liquibase/ITLiquibaseTest.java | 1 +
4 files changed, 55 insertions(+), 92 deletions(-)
diff --git a/samples/java/hibernate/pom.xml b/samples/java/hibernate/pom.xml
index 254e139d6b..5544b92358 100644
--- a/samples/java/hibernate/pom.xml
+++ b/samples/java/hibernate/pom.xml
@@ -58,52 +58,4 @@
-
-
-