This is an automated email from the ASF dual-hosted git repository.
terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new c11a89b038a Fix PostgreSQL composite column type OID resolution
(#39241)
c11a89b038a is described below
commit c11a89b038ac984f79c28280bcd52280f6857587
Author: Liang Zhang <[email protected]>
AuthorDate: Mon Jul 27 14:26:16 2026 +0800
Fix PostgreSQL composite column type OID resolution (#39241)
* Fix PostgreSQL composite column type OID resolution
* Fix PostgreSQL composite column type OID resolution
* Fix PostgreSQL composite type OID routing
- resolve composite type OIDs from the actual routed data source
- reject ambiguous composite OIDs across multiple data sources
- document error 30031 and fix column metadata argument order
* Fix PostgreSQL composite type OID routing
- resolve composite type OIDs from the actual routed data source
- reject ambiguous composite OIDs across multiple data sources
- document error 30031 and fix column metadata argument order
* Fix PostgreSQL composite type OID routing
- resolve composite type OIDs from the actual routed data source
- reject ambiguous composite OIDs across multiple data sources
- document error 30031 and fix column metadata argument order
* Fix PostgreSQL composite type OID routing
- resolve composite type OIDs from the actual routed data source
- reject ambiguous composite OIDs across multiple data sources
- document error 30031 and fix column metadata argument order
* Fix PostgreSQL composite type OID routing
- resolve composite type OIDs from the actual routed data source
- reject ambiguous composite OIDs across multiple data sources
- document error 30031 and fix column metadata argument order
* Fix PostgreSQL composite type OID routing
- resolve composite type OIDs from the actual routed data source
- reject ambiguous composite OIDs across multiple data sources
- document error 30031 and fix column metadata argument order
* Fix incorrect PostgreSQL composite column type OIDs
- load composite type OIDs from the executed JDBC result connection
- propagate PostgreSQL type OIDs through query header attributes
- reject cross-storage composite results before execution
* Fix incorrect PostgreSQL composite column type OIDs
- load composite type OIDs from the executed JDBC result connection
- propagate PostgreSQL type OIDs through query header attributes
- reject cross-storage composite results before execution
* Fix incorrect PostgreSQL composite column type OIDs
- load composite type OIDs from the executed JDBC result connection
- propagate PostgreSQL type OIDs through query header attributes
- reject cross-storage composite results before execution
---
RELEASE-NOTES.md | 1 +
.../command/query/PostgreSQLColumnDescription.java | 6 +-
.../type/PostgreSQLColumnTypeOIDLoader.java | 81 ++++++++++++++++
.../query/PostgreSQLColumnDescriptionTest.java | 7 ++
.../type/PostgreSQLColumnTypeOIDLoaderTest.java | 84 ++++++++++++++++
.../proxy/backend/connector/ProxySQLExecutor.java | 9 ++
.../connector/StandardDatabaseProxyConnector.java | 12 +--
.../DialectJDBCResultMetadataChecker.java} | 24 +++--
.../backend/response/header/query/QueryHeader.java | 10 ++
.../response/header/query/QueryHeaderBuilder.java | 18 ++++
.../header/query/QueryHeaderBuilderEngine.java | 25 ++++-
.../backend/connector/ProxySQLExecutorTest.java | 34 +++++++
.../StandardDatabaseProxyConnectorTest.java | 10 +-
.../header/query/QueryHeaderBuilderEngineTest.java | 28 +++++-
.../jdbc/PostgreSQLJDBCResultMetadataChecker.java | 66 +++++++++++++
.../header/query/PostgreSQLQueryHeaderBuilder.java | 30 +++++-
....jdbc.executor.DialectJDBCResultMetadataChecker | 18 ++++
.../PostgreSQLJDBCResultMetadataCheckerTest.java | 106 +++++++++++++++++++++
.../query/PostgreSQLQueryHeaderBuilderTest.java | 37 +++++++
.../prepare/MySQLProjectionMetadataResolver.java | 4 +-
.../postgresql/command/query/extended/Portal.java | 7 +-
...PostgreSQLPreparedStatementMetadataFactory.java | 21 +++-
.../describe/PostgreSQLComDescribeExecutor.java | 46 +++++++--
.../query/simple/PostgreSQLComQueryExecutor.java | 8 +-
.../command/query/extended/PortalTest.java | 14 ++-
...greSQLPreparedStatementMetadataFactoryTest.java | 36 +++++++
.../PostgreSQLComDescribeExecutorTest.java | 48 ++++++++--
.../simple/PostgreSQLComQueryExecutorTest.java | 8 +-
28 files changed, 738 insertions(+), 60 deletions(-)
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index adfc1080407..4a78208c24d 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -36,6 +36,7 @@
1. Proxy: Add MySQL exception mapping for ColumnNotFoundException -
[#39126](https://github.com/apache/shardingsphere/pull/39126)
1. Proxy: Fix MySQL prepared statement parameter signedness decoding -
[#39204](https://github.com/apache/shardingsphere/pull/39204)
1. Proxy: Fix Proxy Native Docker image failing to start due to unexpanded
LOCAL_PATH in ENTRYPOINT -
[#39146](https://github.com/apache/shardingsphere/pull/39146)
+1. Proxy: Fix incorrect PostgreSQL composite column type OIDs in simple and
extended query row descriptions -
[#39241](https://github.com/apache/shardingsphere/pull/39241)
1. JDBC & Proxy: Remove default MySQL prepared statement query properties when
creating data sources -
[#38593](https://github.com/apache/shardingsphere/pull/38593)
1. Mode: Fix rule metadata not removed from memory after dropping rules in
Etcd cluster mode -
[#38561](https://github.com/apache/shardingsphere/pull/38561)
1. Agent: Fix wrong target class name in StaticMethodAdviceExecutor error logs
- [#39077](https://github.com/apache/shardingsphere/pull/39077)
diff --git
a/database/protocol/dialect/postgresql/src/main/java/org/apache/shardingsphere/database/protocol/postgresql/packet/command/query/PostgreSQLColumnDescription.java
b/database/protocol/dialect/postgresql/src/main/java/org/apache/shardingsphere/database/protocol/postgresql/packet/command/query/PostgreSQLColumnDescription.java
index 2c6d16729a0..43ed351fc43 100644
---
a/database/protocol/dialect/postgresql/src/main/java/org/apache/shardingsphere/database/protocol/postgresql/packet/command/query/PostgreSQLColumnDescription.java
+++
b/database/protocol/dialect/postgresql/src/main/java/org/apache/shardingsphere/database/protocol/postgresql/packet/command/query/PostgreSQLColumnDescription.java
@@ -18,6 +18,7 @@
package
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query;
import lombok.Getter;
+import lombok.RequiredArgsConstructor;
import
org.apache.shardingsphere.database.protocol.postgresql.constant.PostgreSQLArrayColumnType;
import
org.apache.shardingsphere.database.protocol.postgresql.constant.PostgreSQLValueFormat;
import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query.extended.PostgreSQLBinaryColumnType;
@@ -27,6 +28,7 @@ import java.sql.Types;
/**
* Column description for PostgreSQL.
*/
+@RequiredArgsConstructor
@Getter
public final class PostgreSQLColumnDescription {
@@ -36,10 +38,10 @@ public final class PostgreSQLColumnDescription {
private final int columnIndex;
- private final int columnLength;
-
private final int typeOID;
+ private final int columnLength;
+
private final int typeModifier = -1;
private final int dataFormat;
diff --git
a/database/protocol/dialect/postgresql/src/main/java/org/apache/shardingsphere/database/protocol/postgresql/type/PostgreSQLColumnTypeOIDLoader.java
b/database/protocol/dialect/postgresql/src/main/java/org/apache/shardingsphere/database/protocol/postgresql/type/PostgreSQLColumnTypeOIDLoader.java
new file mode 100644
index 00000000000..07f1a6ba7e6
--- /dev/null
+++
b/database/protocol/dialect/postgresql/src/main/java/org/apache/shardingsphere/database/protocol/postgresql/type/PostgreSQLColumnTypeOIDLoader.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shardingsphere.database.protocol.postgresql.type;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.postgresql.core.BaseConnection;
+import org.postgresql.core.Oid;
+
+import java.sql.Connection;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Loader for PostgreSQL column type OIDs.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class PostgreSQLColumnTypeOIDLoader {
+
+ /**
+ * Load composite column type OIDs from result set metadata.
+ *
+ * @param connection database connection
+ * @param metaData result set metadata
+ * @return column indexes to type OIDs, or an empty map if no composite
column type can be resolved
+ * @throws SQLException SQL exception
+ */
+ public static Map<Integer, Integer> load(final Connection connection,
final ResultSetMetaData metaData) throws SQLException {
+ return connection.isWrapperFor(BaseConnection.class) ?
load(connection.unwrap(BaseConnection.class), metaData) :
Collections.emptyMap();
+ }
+
+ private static Map<Integer, Integer> load(final BaseConnection connection,
final ResultSetMetaData metaData) throws SQLException {
+ int columnCount = metaData.getColumnCount();
+ Map<Integer, Integer> result = new HashMap<>();
+ for (int columnIndex = 1; columnIndex <= columnCount; columnIndex++) {
+ if (Types.STRUCT == metaData.getColumnType(columnIndex)) {
+ int typeOID =
connection.getTypeInfo().getPGType(metaData.getColumnTypeName(columnIndex));
+ if (Oid.UNSPECIFIED != typeOID) {
+ result.put(columnIndex, typeOID);
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Find type OID.
+ *
+ * @param connection database connection
+ * @param columnTypeName column type name
+ * @return type OID
+ * @throws SQLException SQL exception
+ */
+ public static Optional<Integer> findTypeOID(final Connection connection,
final String columnTypeName) throws SQLException {
+ if (!connection.isWrapperFor(BaseConnection.class)) {
+ return Optional.empty();
+ }
+ int typeOID =
connection.unwrap(BaseConnection.class).getTypeInfo().getPGType(columnTypeName);
+ return Oid.UNSPECIFIED == typeOID ? Optional.empty() :
Optional.of(typeOID);
+ }
+}
diff --git
a/database/protocol/dialect/postgresql/src/test/java/org/apache/shardingsphere/database/protocol/postgresql/packet/command/query/PostgreSQLColumnDescriptionTest.java
b/database/protocol/dialect/postgresql/src/test/java/org/apache/shardingsphere/database/protocol/postgresql/packet/command/query/PostgreSQLColumnDescriptionTest.java
index a819e62d283..c986e525063 100644
---
a/database/protocol/dialect/postgresql/src/test/java/org/apache/shardingsphere/database/protocol/postgresql/packet/command/query/PostgreSQLColumnDescriptionTest.java
+++
b/database/protocol/dialect/postgresql/src/test/java/org/apache/shardingsphere/database/protocol/postgresql/packet/command/query/PostgreSQLColumnDescriptionTest.java
@@ -26,6 +26,13 @@ import static org.hamcrest.MatcherAssert.assertThat;
class PostgreSQLColumnDescriptionTest {
+ @Test
+ void assertNativeTypeOid() {
+ PostgreSQLColumnDescription description = new
PostgreSQLColumnDescription("record_value", 1, 2249, 32, 0);
+ assertThat(description.getTypeOID(), is(2249));
+ assertThat(description.getColumnLength(), is(32));
+ }
+
@Test
void assertIntegerTypeOid() {
PostgreSQLColumnDescription description = new
PostgreSQLColumnDescription("age", 1, Types.INTEGER, 4, null);
diff --git
a/database/protocol/dialect/postgresql/src/test/java/org/apache/shardingsphere/database/protocol/postgresql/type/PostgreSQLColumnTypeOIDLoaderTest.java
b/database/protocol/dialect/postgresql/src/test/java/org/apache/shardingsphere/database/protocol/postgresql/type/PostgreSQLColumnTypeOIDLoaderTest.java
new file mode 100644
index 00000000000..9d0cfb6b2e4
--- /dev/null
+++
b/database/protocol/dialect/postgresql/src/test/java/org/apache/shardingsphere/database/protocol/postgresql/type/PostgreSQLColumnTypeOIDLoaderTest.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shardingsphere.database.protocol.postgresql.type;
+
+import org.junit.jupiter.api.Test;
+import org.postgresql.core.BaseConnection;
+import org.postgresql.core.Oid;
+import org.postgresql.core.TypeInfo;
+
+import java.sql.Connection;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class PostgreSQLColumnTypeOIDLoaderTest {
+
+ @Test
+ void assertLoadFromResultSetMetaData() throws SQLException {
+ BaseConnection connection = mockConnection("record_type", 2249);
+ ResultSetMetaData metaData = mock(ResultSetMetaData.class);
+ when(metaData.getColumnCount()).thenReturn(2);
+ when(metaData.getColumnType(1)).thenReturn(Types.STRUCT);
+ when(metaData.getColumnType(2)).thenReturn(Types.VARCHAR);
+ when(metaData.getColumnTypeName(1)).thenReturn("record_type");
+ Map<Integer, Integer> actual =
PostgreSQLColumnTypeOIDLoader.load(connection, metaData);
+ assertThat(actual, is(Collections.singletonMap(1, 2249)));
+ verify(metaData, never()).getColumnTypeName(2);
+ }
+
+ @Test
+ void assertLoadFromNonPostgreSQLConnection() throws SQLException {
+ Connection connection = mock(Connection.class);
+ assertTrue(PostgreSQLColumnTypeOIDLoader.load(connection,
mock(ResultSetMetaData.class)).isEmpty());
+ }
+
+ @Test
+ void assertFindUnspecifiedTypeOID() throws SQLException {
+ BaseConnection connection = mockConnection("unknown_type",
Oid.UNSPECIFIED);
+ assertFalse(PostgreSQLColumnTypeOIDLoader.findTypeOID(connection,
"unknown_type").isPresent());
+ }
+
+ @Test
+ void assertFindTypeOID() throws SQLException {
+ BaseConnection connection = mockConnection("record_type", 2249);
+ assertThat(PostgreSQLColumnTypeOIDLoader.findTypeOID(connection,
"record_type"), is(Optional.of(2249)));
+ }
+
+ private BaseConnection mockConnection(final String columnTypeName, final
int typeOID) throws SQLException {
+ TypeInfo typeInfo = mock(TypeInfo.class);
+ when(typeInfo.getPGType(columnTypeName)).thenReturn(typeOID);
+ BaseConnection result = mock(BaseConnection.class);
+ when(result.getTypeInfo()).thenReturn(typeInfo);
+ when(result.isWrapperFor(BaseConnection.class)).thenReturn(true);
+ when(result.unwrap(BaseConnection.class)).thenReturn(result);
+ return result;
+ }
+}
diff --git
a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/ProxySQLExecutor.java
b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/ProxySQLExecutor.java
index 4e9a3b9c4e2..fe389841780 100644
---
a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/ProxySQLExecutor.java
+++
b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/ProxySQLExecutor.java
@@ -56,6 +56,7 @@ import
org.apache.shardingsphere.infra.session.connection.transaction.Transactio
import org.apache.shardingsphere.infra.spi.type.ordered.OrderedSPILoader;
import org.apache.shardingsphere.mode.metadata.MetaDataContexts;
import
org.apache.shardingsphere.proxy.backend.connector.jdbc.executor.ProxyJDBCExecutor;
+import
org.apache.shardingsphere.proxy.backend.connector.jdbc.executor.DialectJDBCResultMetadataChecker;
import
org.apache.shardingsphere.proxy.backend.connector.jdbc.statement.JDBCBackendStatement;
import
org.apache.shardingsphere.proxy.backend.connector.sane.DialectSaneQueryResultEngine;
import org.apache.shardingsphere.proxy.backend.context.BackendExecutorContext;
@@ -238,6 +239,14 @@ public final class ProxySQLExecutor {
} catch (final SQLException ex) {
return getSaneExecuteResults(executionContext, ex);
}
+ if (executionContext.getExecutionUnits().size() > 1) {
+ DatabaseType databaseType =
ProxyContext.getInstance().getContextManager().getDatabase(databaseName).getProtocolType();
+ Optional<DialectJDBCResultMetadataChecker> checker =
DatabaseTypedSPILoader.findService(DialectJDBCResultMetadataChecker.class,
databaseType);
+ if (checker.isPresent()) {
+ JDBCExecutionUnit sample =
executionGroupContext.getInputGroups().iterator().next().getInputs().get(0);
+ checker.get().check(executionContext.getExecutionUnits(),
sample.getStorageResource(), sample.getExecutionUnit().getSqlUnit().getSql());
+ }
+ }
executeTransactionHooksBeforeExecuteSQL(databaseConnectionManager.getConnectionSession());
return regularExecutor.execute(executionContext.getQueryContext(),
executionGroupContext, isReturnGeneratedKeys, isExceptionThrown);
}
diff --git
a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnector.java
b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnector.java
index 812d30fb76b..b40aeaae72e 100644
---
a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnector.java
+++
b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnector.java
@@ -301,10 +301,12 @@ public final class StandardDatabaseProxyConnector
implements DatabaseProxyConnec
int columnCount = getColumnCount(sqlStatementContext,
queryResultSample);
List<QueryHeader> result = new ArrayList<>(columnCount);
QueryHeaderBuilderEngine queryHeaderBuilderEngine = new
QueryHeaderBuilderEngine(database.getProtocolType());
- Collection<ShardingSphereDatabase> databases =
queryContext.getMetaData().getAllDatabases();
ShardingSphereResultSetMetaData resultSetMetaData = new
ShardingSphereResultSetMetaData(queryResultSample.getMetaData().getResultSetMetaData(),
database, sqlStatementContext);
+ Optional<ResultSet> resultSetSample =
cachedResultSets.stream().findFirst();
for (int columnIndex = 1; columnIndex <= columnCount; columnIndex++) {
- result.add(createQueryHeader(queryHeaderBuilderEngine,
sqlStatementContext, resultSetMetaData, database, databases, columnIndex));
+ result.add(resultSetSample.isPresent()
+ ? queryHeaderBuilderEngine.build(sqlStatementContext,
resultSetMetaData, resultSetSample.get(), database, columnIndex)
+ : queryHeaderBuilderEngine.build(sqlStatementContext,
resultSetMetaData, database, columnIndex));
}
return result;
}
@@ -315,12 +317,6 @@ public final class StandardDatabaseProxyConnector
implements DatabaseProxyConnec
: queryResultSample.getMetaData().getColumnCount();
}
- private QueryHeader createQueryHeader(final QueryHeaderBuilderEngine
queryHeaderBuilderEngine, final SQLStatementContext sqlStatementContext,
- final
ShardingSphereResultSetMetaData resultSetMetaData, final ShardingSphereDatabase
database,
- final
Collection<ShardingSphereDatabase> databases, final int columnIndex) throws
SQLException {
- return queryHeaderBuilderEngine.build(sqlStatementContext,
resultSetMetaData, database, databases, columnIndex);
- }
-
private MergedResult mergeQuery(final List<QueryResult> queryResults)
throws SQLException {
MergeEngine mergeEngine = new
MergeEngine(contextManager.getMetaDataContexts().getMetaData(),
database,
contextManager.getMetaDataContexts().getMetaData().getProps(),
databaseConnectionManager.getConnectionSession().getConnectionContext());
diff --git
a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilder.java
b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/jdbc/executor/DialectJDBCResultMetadataChecker.java
similarity index 58%
copy from
proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilder.java
copy to
proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/jdbc/executor/DialectJDBCResultMetadataChecker.java
index 0eeeab6658b..d1fa5935a0c 100644
---
a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilder.java
+++
b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/connector/jdbc/executor/DialectJDBCResultMetadataChecker.java
@@ -15,31 +15,29 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.proxy.backend.response.header.query;
+package org.apache.shardingsphere.proxy.backend.connector.jdbc.executor;
import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPI;
-import
org.apache.shardingsphere.driver.jdbc.core.resultset.ShardingSphereResultSetMetaData;
-import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
+import org.apache.shardingsphere.infra.executor.sql.context.ExecutionUnit;
import org.apache.shardingsphere.infra.spi.annotation.SingletonSPI;
import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Collection;
/**
- * Query header builder.
+ * Dialect JDBC result metadata checker.
*/
@SingletonSPI
-public interface QueryHeaderBuilder extends DatabaseTypedSPI {
+public interface DialectJDBCResultMetadataChecker extends DatabaseTypedSPI {
/**
- * Build query header.
+ * Check result metadata before execution.
*
- * @param resultSetMetaData result set meta data
- * @param database database
- * @param columnName column name
- * @param columnLabel column label
- * @param columnIndex column index
- * @return query header
+ * @param executionUnits execution units
+ * @param statement JDBC statement
+ * @param sql routed SQL corresponding to the statement
* @throws SQLException SQL exception
*/
- QueryHeader build(ShardingSphereResultSetMetaData resultSetMetaData,
ShardingSphereDatabase database, String columnName, String columnLabel, int
columnIndex) throws SQLException;
+ void check(Collection<ExecutionUnit> executionUnits, Statement statement,
String sql) throws SQLException;
}
diff --git
a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeader.java
b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeader.java
index 9feed9ac35e..d8d756b5ef3 100644
---
a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeader.java
+++
b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeader.java
@@ -20,6 +20,9 @@ package
org.apache.shardingsphere.proxy.backend.response.header.query;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
+import java.util.Collections;
+import java.util.Map;
+
/**
* Query header.
*/
@@ -50,4 +53,11 @@ public final class QueryHeader {
private final boolean notNull;
private final boolean autoIncrement;
+
+ private final Map<String, Object> protocolAttributes;
+
+ public QueryHeader(final String schema, final String table, final String
columnLabel, final String columnName, final int columnType, final String
columnTypeName,
+ final int columnLength, final int decimals, final
boolean signed, final boolean primaryKey, final boolean notNull, final boolean
autoIncrement) {
+ this(schema, table, columnLabel, columnName, columnType,
columnTypeName, columnLength, decimals, signed, primaryKey, notNull,
autoIncrement, Collections.emptyMap());
+ }
}
diff --git
a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilder.java
b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilder.java
index 0eeeab6658b..26a7a92099b 100644
---
a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilder.java
+++
b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilder.java
@@ -22,6 +22,7 @@ import
org.apache.shardingsphere.driver.jdbc.core.resultset.ShardingSphereResult
import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
import org.apache.shardingsphere.infra.spi.annotation.SingletonSPI;
+import java.sql.ResultSet;
import java.sql.SQLException;
/**
@@ -42,4 +43,21 @@ public interface QueryHeaderBuilder extends DatabaseTypedSPI
{
* @throws SQLException SQL exception
*/
QueryHeader build(ShardingSphereResultSetMetaData resultSetMetaData,
ShardingSphereDatabase database, String columnName, String columnLabel, int
columnIndex) throws SQLException;
+
+ /**
+ * Build query header.
+ *
+ * @param resultSetMetaData result set meta data
+ * @param resultSet JDBC result set
+ * @param database database
+ * @param columnName column name
+ * @param columnLabel column label
+ * @param columnIndex column index
+ * @return query header
+ * @throws SQLException SQL exception
+ */
+ default QueryHeader build(final ShardingSphereResultSetMetaData
resultSetMetaData, final ResultSet resultSet, final ShardingSphereDatabase
database,
+ final String columnName, final String
columnLabel, final int columnIndex) throws SQLException {
+ return build(resultSetMetaData, database, columnName, columnLabel,
columnIndex);
+ }
}
diff --git
a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilderEngine.java
b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilderEngine.java
index dd1ec10889e..24a3546bdcc 100644
---
a/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilderEngine.java
+++
b/proxy/backend/core/src/main/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilderEngine.java
@@ -27,8 +27,8 @@ import
org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
import
org.apache.shardingsphere.infra.exception.kernel.syntax.ColumnIndexOutOfRangeException;
import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
+import java.sql.ResultSet;
import java.sql.SQLException;
-import java.util.Collection;
/**
* Query header builder engine.
@@ -62,19 +62,38 @@ public final class QueryHeaderBuilderEngine {
* @param sqlStatementContext SQL statement context
* @param resultSetMetaData result set meta data
* @param database current database
- * @param databases available databases
* @param columnIndex column index
* @return query header
* @throws SQLException SQL exception
*/
public QueryHeader build(final SQLStatementContext sqlStatementContext,
final ShardingSphereResultSetMetaData resultSetMetaData, final
ShardingSphereDatabase database,
- final Collection<ShardingSphereDatabase>
databases, final int columnIndex) throws SQLException {
+ final int columnIndex) throws SQLException {
Projection projection = findProjection(sqlStatementContext,
columnIndex);
return null == projection
? build(resultSetMetaData, database, columnIndex)
: queryHeaderBuilder.build(resultSetMetaData, database,
projection.getColumnName(), projection.getColumnLabel(), columnIndex);
}
+ /**
+ * Build query header builder.
+ *
+ * @param sqlStatementContext SQL statement context
+ * @param resultSetMetaData result set meta data
+ * @param resultSet JDBC result set
+ * @param database current database
+ * @param columnIndex column index
+ * @return query header
+ * @throws SQLException SQL exception
+ */
+ public QueryHeader build(final SQLStatementContext sqlStatementContext,
final ShardingSphereResultSetMetaData resultSetMetaData, final ResultSet
resultSet,
+ final ShardingSphereDatabase database, final int
columnIndex) throws SQLException {
+ Projection projection = findProjection(sqlStatementContext,
columnIndex);
+ if (null == projection) {
+ return queryHeaderBuilder.build(resultSetMetaData, resultSet,
database, resultSetMetaData.getColumnName(columnIndex),
resultSetMetaData.getColumnLabel(columnIndex), columnIndex);
+ }
+ return queryHeaderBuilder.build(resultSetMetaData, resultSet,
database, projection.getColumnName(), projection.getColumnLabel(), columnIndex);
+ }
+
private Projection findProjection(final SQLStatementContext
sqlStatementContext, final int columnIndex) {
if (!(sqlStatementContext instanceof SelectStatementContext) ||
!((SelectStatementContext) sqlStatementContext).containsDerivedProjections()) {
return null;
diff --git
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/ProxySQLExecutorTest.java
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/ProxySQLExecutorTest.java
index 9d9a64e68e7..962309c91fc 100644
---
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/ProxySQLExecutorTest.java
+++
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/ProxySQLExecutorTest.java
@@ -32,9 +32,13 @@ import
org.apache.shardingsphere.infra.binder.context.statement.SQLStatementCont
import
org.apache.shardingsphere.infra.binder.context.statement.type.dml.InsertStatementContext;
import org.apache.shardingsphere.infra.config.props.ConfigurationPropertyKey;
import org.apache.shardingsphere.infra.config.rule.RuleConfiguration;
+import org.apache.shardingsphere.infra.executor.kernel.model.ExecutionGroup;
import
org.apache.shardingsphere.infra.executor.kernel.model.ExecutionGroupContext;
import
org.apache.shardingsphere.infra.executor.kernel.model.ExecutionGroupReportContext;
import org.apache.shardingsphere.infra.executor.sql.context.ExecutionContext;
+import org.apache.shardingsphere.infra.executor.sql.context.ExecutionUnit;
+import org.apache.shardingsphere.infra.executor.sql.context.SQLUnit;
+import
org.apache.shardingsphere.infra.executor.sql.execute.engine.ConnectionMode;
import
org.apache.shardingsphere.infra.executor.sql.execute.engine.driver.jdbc.JDBCExecutionUnit;
import
org.apache.shardingsphere.infra.executor.sql.execute.engine.raw.RawExecutor;
import
org.apache.shardingsphere.infra.executor.sql.execute.engine.raw.RawSQLExecutionUnit;
@@ -54,6 +58,7 @@ import
org.apache.shardingsphere.infra.session.connection.transaction.Transactio
import org.apache.shardingsphere.infra.session.query.QueryContext;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.mode.manager.ContextManager;
+import
org.apache.shardingsphere.proxy.backend.connector.jdbc.executor.DialectJDBCResultMetadataChecker;
import
org.apache.shardingsphere.proxy.backend.connector.jdbc.executor.ProxyJDBCExecutor;
import
org.apache.shardingsphere.proxy.backend.connector.jdbc.statement.JDBCBackendStatement;
import
org.apache.shardingsphere.proxy.backend.connector.sane.DialectSaneQueryResultEngine;
@@ -98,6 +103,7 @@ import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import java.sql.SQLException;
+import java.sql.Statement;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -116,6 +122,7 @@ import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyCollection;
import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockConstruction;
@@ -315,6 +322,33 @@ class ProxySQLExecutorTest {
Arguments.of("execute-with-driver-and-no-transaction", false,
createInsertStatement(postgresqlDatabaseType), false, false, false));
}
+ @Test
+ void assertExecuteWithDialectResultMetadataCheckerException() throws
SQLException {
+ when(connectionSession.getUsedDatabaseName()).thenReturn("foo_db");
+
when(database.getRuleMetaData().getRules()).thenReturn(createRules(false));
+ ProxySQLExecutor proxySQLExecutor =
createProxySQLExecutor("foo_schema", true);
+ setExecutorField(proxySQLExecutor, "regularExecutor", regularExecutor);
+ ExecutionContext executionContext =
createExecutionContext("execute-with-dialect-result-metadata-checker", new
SQLStatement(fixtureDatabaseType), false);
+ Statement statement = mock(Statement.class);
+ ExecutionUnit executionUnit = new ExecutionUnit("foo_ds", new
SQLUnit("SELECT 1", Collections.emptyList()));
+ Collection<ExecutionUnit> executionUnits =
Arrays.asList(executionUnit, new ExecutionUnit("foo_ds_1", new SQLUnit("SELECT
1", Collections.emptyList())));
+ when(executionContext.getExecutionUnits()).thenReturn(executionUnits);
+ JDBCExecutionUnit jdbcExecutionUnit = new
JDBCExecutionUnit(executionUnit, ConnectionMode.CONNECTION_STRICTLY, statement);
+ ExecutionGroupContext<JDBCExecutionUnit> executionGroupContext = new
ExecutionGroupContext<>(
+ Collections.singletonList(new
ExecutionGroup<>(Collections.singletonList(jdbcExecutionUnit))),
mock(ExecutionGroupReportContext.class));
+ DialectJDBCResultMetadataChecker checker =
mock(DialectJDBCResultMetadataChecker.class);
+ SQLException expected = new SQLException("expected");
+ doThrow(expected).when(checker).check(executionUnits, statement,
"SELECT 1");
+ try (
+ MockedConstruction<DriverExecutionPrepareEngine> ignored =
mockConstruction(DriverExecutionPrepareEngine.class,
+ (mock, context) -> when(mock.prepare(anyString(),
eq(executionContext), anyCollection(),
any(ExecutionGroupReportContext.class))).thenReturn(executionGroupContext));
+ MockedStatic<DatabaseTypedSPILoader> spiLoader =
mockStatic(DatabaseTypedSPILoader.class, CALLS_REAL_METHODS)) {
+ spiLoader.when(() ->
DatabaseTypedSPILoader.findService(DialectJDBCResultMetadataChecker.class,
fixtureDatabaseType)).thenReturn(Optional.of(checker));
+ assertThat(assertThrows(SQLException.class, () ->
proxySQLExecutor.execute(executionContext)), is(expected));
+ verify(regularExecutor, never()).execute(any(), any(),
anyBoolean(), anyBoolean());
+ }
+ }
+
@ParameterizedTest(name = "{0}")
@MethodSource("executeFallbackScenarios")
void assertExecuteFallback(final String name, final boolean
hasRawExecutionRule, final SQLStatement sqlStatement, final boolean
hasSaneResult) throws SQLException {
diff --git
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnectorTest.java
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnectorTest.java
index fd947f30c34..638ddd37c75 100644
---
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnectorTest.java
+++
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/StandardDatabaseProxyConnectorTest.java
@@ -24,6 +24,7 @@ import
org.apache.shardingsphere.database.connector.core.metadata.database.metad
import
org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import
org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
+import
org.apache.shardingsphere.driver.jdbc.core.resultset.ShardingSphereResultSetMetaData;
import
org.apache.shardingsphere.infra.binder.context.segment.insert.keygen.GeneratedKeyContext;
import
org.apache.shardingsphere.infra.binder.context.segment.select.projection.Projection;
import
org.apache.shardingsphere.infra.binder.context.segment.select.projection.ProjectionsContext;
@@ -71,6 +72,7 @@ import
org.apache.shardingsphere.proxy.backend.connector.jdbc.transaction.ProxyB
import org.apache.shardingsphere.proxy.backend.context.BackendExecutorContext;
import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
import org.apache.shardingsphere.proxy.backend.response.header.ResponseHeader;
+import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryHeader;
import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryHeaderBuilder;
import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryResponseHeader;
import
org.apache.shardingsphere.proxy.backend.response.header.update.UpdateResponseHeader;
@@ -110,6 +112,7 @@ import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
+import java.sql.Types;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -500,12 +503,16 @@ class StandardDatabaseProxyConnectorTest {
when(resultSetMetaData.getColumnLabel(1)).thenReturn("order_id");
when(queryResult.getMetaData()).thenReturn(queryResultMetaData);
when(proxySQLExecutor.execute(executionContext)).thenReturn(Collections.singletonList(queryResult));
+ engine.add(resultSet);
MergedResult mergedResult = mock(MergedResult.class);
when(mergedResult.next()).thenReturn(true);
when(mergedResult.getValue(1, Object.class)).thenReturn(1);
DialectDatabaseMetaData dialectDatabaseMetaData =
mock(DialectDatabaseMetaData.class);
DialectTransactionOption dialectTransactionOption =
mock(DialectTransactionOption.class);
when(dialectDatabaseMetaData.getTransactionOption()).thenReturn(dialectTransactionOption);
+ QueryHeaderBuilder queryHeaderBuilder = mock(QueryHeaderBuilder.class);
+ QueryHeader queryHeader = new QueryHeader("", "", "order_id",
"order_id", Types.INTEGER, "int4", 4, 0, true, false, false, false);
+
when(queryHeaderBuilder.build(any(ShardingSphereResultSetMetaData.class),
eq(resultSet), any(), eq("order_id"), eq("order_id"),
eq(1))).thenReturn(queryHeader);
try (
MockedConstruction<KernelProcessor> mockedKernelProcessor =
mockConstruction(KernelProcessor.class,
(mock, context) ->
when(mock.generateExecutionContext(any(QueryContext.class),
any(RuleMetaData.class),
any(ConfigurationProperties.class))).thenReturn(executionContext));
@@ -515,7 +522,7 @@ class StandardDatabaseProxyConnectorTest {
mockConstruction(MergeEngine.class, (mock, context) ->
when(mock.merge(anyList(), any(QueryContext.class))).thenReturn(mergedResult));
MockedStatic<DatabaseTypedSPILoader> spiLoader =
mockStatic(DatabaseTypedSPILoader.class);
MockedStatic<ShardingSphereServiceLoader> serviceLoader =
mockStatic(ShardingSphereServiceLoader.class)) {
- spiLoader.when(() ->
DatabaseTypedSPILoader.getService(eq(QueryHeaderBuilder.class),
any(DatabaseType.class))).thenReturn(new QueryHeaderBuilderFixture());
+ spiLoader.when(() ->
DatabaseTypedSPILoader.getService(eq(QueryHeaderBuilder.class),
any(DatabaseType.class))).thenReturn(queryHeaderBuilder);
serviceLoader.when(() ->
ShardingSphereServiceLoader.getServiceInstances(AdvancedProxySQLExecutor.class)).thenReturn(Collections.emptyList());
assertThat(engine.execute(), isA(QueryResponseHeader.class));
assertThat(mockedKernelProcessor.constructed().size(), is(1));
@@ -523,6 +530,7 @@ class StandardDatabaseProxyConnectorTest {
assertThat(mockedMergeEngine.constructed().size(), is(1));
assertTrue(engine.next());
assertNotNull(engine.getRowData());
+
verify(queryHeaderBuilder).build(any(ShardingSphereResultSetMetaData.class),
eq(resultSet), any(), eq("order_id"), eq("order_id"), eq(1));
}
}
diff --git
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilderEngineTest.java
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilderEngineTest.java
index 58b13bbf844..df98fc08b3f 100644
---
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilderEngineTest.java
+++
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/response/header/query/QueryHeaderBuilderEngineTest.java
@@ -30,6 +30,7 @@ import
org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
+import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
@@ -77,7 +78,26 @@ class QueryHeaderBuilderEngineTest {
QueryHeaderBuilder queryHeaderBuilder =
mock(QueryHeaderBuilder.class);
when(queryHeaderBuilder.build(resultSetMetaData, database, "c1",
"l1", 1)).thenReturn(expectedQueryHeader);
spiLoader.when(() ->
DatabaseTypedSPILoader.getService(QueryHeaderBuilder.class,
databaseType)).thenReturn(queryHeaderBuilder);
- QueryHeader actualQueryHeader = new
QueryHeaderBuilderEngine(databaseType).build(sqlStatementContext,
resultSetMetaData, database, Collections.singleton(database), 1);
+ QueryHeader actualQueryHeader = new
QueryHeaderBuilderEngine(databaseType).build(sqlStatementContext,
resultSetMetaData, database, 1);
+ assertThat(actualQueryHeader, is(expectedQueryHeader));
+ }
+ }
+
+ @Test
+ void assertBuildWithJDBCResultSet() throws SQLException {
+ SQLStatementContext sqlStatementContext =
mock(SQLStatementContext.class);
+ ShardingSphereResultSetMetaData resultSetMetaData =
mock(ShardingSphereResultSetMetaData.class);
+ when(resultSetMetaData.getColumnName(1)).thenReturn("col_name");
+ when(resultSetMetaData.getColumnLabel(1)).thenReturn("col_label");
+ ResultSet resultSet = mock(ResultSet.class);
+ ShardingSphereDatabase database = mock(ShardingSphereDatabase.class);
+ QueryHeader expectedQueryHeader = mock(QueryHeader.class);
+ try (MockedStatic<DatabaseTypedSPILoader> spiLoader =
mockStatic(DatabaseTypedSPILoader.class)) {
+ QueryHeaderBuilder queryHeaderBuilder =
mock(QueryHeaderBuilder.class);
+ when(queryHeaderBuilder.build(resultSetMetaData, resultSet,
database, "col_name", "col_label", 1)).thenReturn(expectedQueryHeader);
+ spiLoader.when(() ->
DatabaseTypedSPILoader.getService(QueryHeaderBuilder.class,
databaseType)).thenReturn(queryHeaderBuilder);
+ QueryHeader actualQueryHeader =
+ new
QueryHeaderBuilderEngine(databaseType).build(sqlStatementContext,
resultSetMetaData, resultSet, database, 1);
assertThat(actualQueryHeader, is(expectedQueryHeader));
}
}
@@ -95,7 +115,7 @@ class QueryHeaderBuilderEngineTest {
try (MockedStatic<DatabaseTypedSPILoader> spiLoader =
mockStatic(DatabaseTypedSPILoader.class)) {
spiLoader.when(() ->
DatabaseTypedSPILoader.getService(QueryHeaderBuilder.class,
databaseType)).thenReturn(mock(QueryHeaderBuilder.class));
assertThrows(ColumnIndexOutOfRangeException.class,
- () -> new
QueryHeaderBuilderEngine(databaseType).build(sqlStatementContext,
mock(ShardingSphereResultSetMetaData.class), mock(),
Collections.singleton(mock()), 2));
+ () -> new
QueryHeaderBuilderEngine(databaseType).build(sqlStatementContext,
mock(ShardingSphereResultSetMetaData.class), mock(), 2));
}
}
@@ -111,7 +131,7 @@ class QueryHeaderBuilderEngineTest {
QueryHeaderBuilder queryHeaderBuilder =
mock(QueryHeaderBuilder.class);
when(queryHeaderBuilder.build(resultSetMetaData, database,
"col_name", "col_label", 1)).thenReturn(expectedQueryHeader);
spiLoader.when(() ->
DatabaseTypedSPILoader.getService(QueryHeaderBuilder.class,
databaseType)).thenReturn(queryHeaderBuilder);
- QueryHeader actualQueryHeader = new
QueryHeaderBuilderEngine(databaseType).build(sqlStatementContext,
resultSetMetaData, database, Collections.singleton(database), 1);
+ QueryHeader actualQueryHeader = new
QueryHeaderBuilderEngine(databaseType).build(sqlStatementContext,
resultSetMetaData, database, 1);
assertThat(actualQueryHeader, is(expectedQueryHeader));
}
}
@@ -129,7 +149,7 @@ class QueryHeaderBuilderEngineTest {
try (MockedStatic<DatabaseTypedSPILoader> spiLoader =
mockStatic(DatabaseTypedSPILoader.class)) {
spiLoader.when(() ->
DatabaseTypedSPILoader.getService(QueryHeaderBuilder.class,
databaseType)).thenReturn(mock(QueryHeaderBuilder.class));
assertThrows(ColumnIndexOutOfRangeException.class,
- () -> new
QueryHeaderBuilderEngine(databaseType).build(sqlStatementContext,
mock(ShardingSphereResultSetMetaData.class), mock(),
Collections.singleton(mock()), 2));
+ () -> new
QueryHeaderBuilderEngine(databaseType).build(sqlStatementContext,
mock(ShardingSphereResultSetMetaData.class), mock(), 2));
}
}
}
diff --git
a/proxy/backend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/backend/postgresql/connector/jdbc/PostgreSQLJDBCResultMetadataChecker.java
b/proxy/backend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/backend/postgresql/connector/jdbc/PostgreSQLJDBCResultMetadataChecker.java
new file mode 100644
index 00000000000..e6462259def
--- /dev/null
+++
b/proxy/backend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/backend/postgresql/connector/jdbc/PostgreSQLJDBCResultMetadataChecker.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shardingsphere.proxy.backend.postgresql.connector.jdbc;
+
+import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
+import
org.apache.shardingsphere.infra.exception.generic.UnsupportedSQLOperationException;
+import org.apache.shardingsphere.infra.executor.sql.context.ExecutionUnit;
+import
org.apache.shardingsphere.proxy.backend.connector.jdbc.executor.DialectJDBCResultMetadataChecker;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.Collection;
+
+/**
+ * JDBC result metadata checker for PostgreSQL.
+ */
+public final class PostgreSQLJDBCResultMetadataChecker implements
DialectJDBCResultMetadataChecker {
+
+ @Override
+ public void check(final Collection<ExecutionUnit> executionUnits, final
Statement statement, final String sql) throws SQLException {
+ if (executionUnits.size() <= 1) {
+ return;
+ }
+ if (statement instanceof PreparedStatement) {
+ checkCompositeType(((PreparedStatement) statement).getMetaData());
+ return;
+ }
+ try (PreparedStatement preparedStatement =
statement.getConnection().prepareStatement(sql)) {
+ checkCompositeType(preparedStatement.getMetaData());
+ }
+ }
+
+ private void checkCompositeType(final ResultSetMetaData metaData) throws
SQLException {
+ if (null == metaData) {
+ return;
+ }
+ int columnCount = metaData.getColumnCount();
+ for (int columnIndex = 1; columnIndex <= columnCount; columnIndex++) {
+ ShardingSpherePreconditions.checkState(Types.STRUCT !=
metaData.getColumnType(columnIndex),
+ () -> new UnsupportedSQLOperationException("PostgreSQL
composite result columns cannot be returned when routed to multiple execution
units"));
+ }
+ }
+
+ @Override
+ public String getDatabaseType() {
+ return "PostgreSQL";
+ }
+}
diff --git
a/proxy/backend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/backend/postgresql/response/header/query/PostgreSQLQueryHeaderBuilder.java
b/proxy/backend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/backend/postgresql/response/header/query/PostgreSQLQueryHeaderBuilder.java
index 1f61a07f488..a3199c6039f 100644
---
a/proxy/backend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/backend/postgresql/response/header/query/PostgreSQLQueryHeaderBuilder.java
+++
b/proxy/backend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/backend/postgresql/response/header/query/PostgreSQLQueryHeaderBuilder.java
@@ -17,18 +17,26 @@
package
org.apache.shardingsphere.proxy.backend.postgresql.response.header.query;
+import
org.apache.shardingsphere.database.protocol.postgresql.type.PostgreSQLColumnTypeOIDLoader;
import
org.apache.shardingsphere.driver.jdbc.core.resultset.ShardingSphereResultSetMetaData;
import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryHeader;
import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryHeaderBuilder;
+import java.sql.ResultSet;
import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
/**
* Query header builder for PostgreSQL.
*/
public final class PostgreSQLQueryHeaderBuilder implements QueryHeaderBuilder {
+ public static final String TYPE_OID = "typeOID";
+
private static final int UNUSED_INT_FIELD = 0;
private static final String UNUSED_STRING_FIELD = "";
@@ -38,11 +46,29 @@ public final class PostgreSQLQueryHeaderBuilder implements
QueryHeaderBuilder {
@Override
public QueryHeader build(final ShardingSphereResultSetMetaData
resultSetMetaData, final ShardingSphereDatabase database, final String
columnName, final String columnLabel,
final int columnIndex) throws SQLException {
+ return createQueryHeader(columnLabel,
+ resultSetMetaData.getColumnType(columnIndex),
resultSetMetaData.getColumnTypeName(columnIndex),
resultSetMetaData.getColumnDisplaySize(columnIndex), Collections.emptyMap());
+ }
+
+ @Override
+ public QueryHeader build(final ShardingSphereResultSetMetaData
resultSetMetaData, final ResultSet resultSet, final ShardingSphereDatabase
database, final String columnName,
+ final String columnLabel, final int columnIndex)
throws SQLException {
int columnType = resultSetMetaData.getColumnType(columnIndex);
String columnTypeName =
resultSetMetaData.getColumnTypeName(columnIndex);
- int columnLength = resultSetMetaData.getColumnDisplaySize(columnIndex);
+ return createQueryHeader(columnLabel, columnType, columnTypeName,
resultSetMetaData.getColumnDisplaySize(columnIndex),
getProtocolAttributes(resultSet, columnType, columnTypeName));
+ }
+
+ private QueryHeader createQueryHeader(final String columnLabel, final int
columnType, final String columnTypeName, final int columnLength, final
Map<String, Object> protocolAttributes) {
return new QueryHeader(UNUSED_STRING_FIELD, UNUSED_STRING_FIELD,
columnLabel, UNUSED_STRING_FIELD, columnType, columnTypeName, columnLength,
- UNUSED_INT_FIELD, UNUSED_BOOLEAN_FIELD, UNUSED_BOOLEAN_FIELD,
UNUSED_BOOLEAN_FIELD, UNUSED_BOOLEAN_FIELD);
+ UNUSED_INT_FIELD, UNUSED_BOOLEAN_FIELD, UNUSED_BOOLEAN_FIELD,
UNUSED_BOOLEAN_FIELD, UNUSED_BOOLEAN_FIELD, protocolAttributes);
+ }
+
+ private Map<String, Object> getProtocolAttributes(final ResultSet
resultSet, final int columnType, final String columnTypeName) throws
SQLException {
+ if (Types.STRUCT != columnType) {
+ return Collections.emptyMap();
+ }
+ Optional<Integer> typeOID =
PostgreSQLColumnTypeOIDLoader.findTypeOID(resultSet.getStatement().getConnection(),
columnTypeName);
+ return typeOID.<Map<String, Object>>map(integer ->
Collections.singletonMap(TYPE_OID, integer)).orElse(Collections.emptyMap());
}
@Override
diff --git
a/proxy/backend/dialect/postgresql/src/main/resources/META-INF/services/org.apache.shardingsphere.proxy.backend.connector.jdbc.executor.DialectJDBCResultMetadataChecker
b/proxy/backend/dialect/postgresql/src/main/resources/META-INF/services/org.apache.shardingsphere.proxy.backend.connector.jdbc.executor.DialectJDBCResultMetadataChecker
new file mode 100644
index 00000000000..cf750c54ea7
--- /dev/null
+++
b/proxy/backend/dialect/postgresql/src/main/resources/META-INF/services/org.apache.shardingsphere.proxy.backend.connector.jdbc.executor.DialectJDBCResultMetadataChecker
@@ -0,0 +1,18 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+
+org.apache.shardingsphere.proxy.backend.postgresql.connector.jdbc.PostgreSQLJDBCResultMetadataChecker
diff --git
a/proxy/backend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/backend/postgresql/connector/jdbc/PostgreSQLJDBCResultMetadataCheckerTest.java
b/proxy/backend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/backend/postgresql/connector/jdbc/PostgreSQLJDBCResultMetadataCheckerTest.java
new file mode 100644
index 00000000000..a1cf1710ac2
--- /dev/null
+++
b/proxy/backend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/backend/postgresql/connector/jdbc/PostgreSQLJDBCResultMetadataCheckerTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shardingsphere.proxy.backend.postgresql.connector.jdbc;
+
+import
org.apache.shardingsphere.infra.exception.generic.UnsupportedSQLOperationException;
+import org.apache.shardingsphere.infra.executor.sql.context.ExecutionUnit;
+import org.apache.shardingsphere.infra.executor.sql.context.SQLUnit;
+import org.junit.jupiter.api.Test;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class PostgreSQLJDBCResultMetadataCheckerTest {
+
+ private final PostgreSQLJDBCResultMetadataChecker checker = new
PostgreSQLJDBCResultMetadataChecker();
+
+ @Test
+ void assertCheckSingleExecutionUnit() throws SQLException {
+ PreparedStatement statement = mock(PreparedStatement.class);
+ checker.check(createExecutionUnits("ds_0"), statement, "SELECT
record_value");
+ verify(statement, never()).getMetaData();
+ }
+
+ @Test
+ void assertCheckMultipleExecutionUnits() throws SQLException {
+ PreparedStatement statement = mock(PreparedStatement.class);
+ ResultSetMetaData metaData = createMetaData(Types.INTEGER);
+ when(statement.getMetaData()).thenReturn(metaData);
+ assertDoesNotThrow(() -> checker.check(createExecutionUnits("ds_0",
"ds_1"), statement, "SELECT value"));
+ }
+
+ @Test
+ void assertCheckCompositeTypeAcrossExecutionUnits() throws SQLException {
+ PreparedStatement statement = mock(PreparedStatement.class);
+ ResultSetMetaData metaData = createMetaData(Types.STRUCT);
+ when(statement.getMetaData()).thenReturn(metaData);
+ UnsupportedSQLOperationException actual =
+ assertThrows(UnsupportedSQLOperationException.class, () ->
checker.check(createExecutionUnits("ds_0", "ds_0"), statement, "SELECT
record_value"));
+ assertThat(actual.getMessage(), is(
+ "Unsupported SQL operation: PostgreSQL composite result
columns cannot be returned when routed to multiple execution units."));
+ }
+
+ @Test
+ void assertCheckStatement() throws SQLException {
+ PreparedStatement preparedStatement = mock(PreparedStatement.class);
+ ResultSetMetaData metaData = createMetaData(Types.INTEGER);
+ when(preparedStatement.getMetaData()).thenReturn(metaData);
+ Connection connection = mock(Connection.class);
+ when(connection.prepareStatement("SELECT
value")).thenReturn(preparedStatement);
+ Statement statement = mock(Statement.class);
+ when(statement.getConnection()).thenReturn(connection);
+ checker.check(createExecutionUnits("ds_0", "ds_1"), statement, "SELECT
value");
+ verify(preparedStatement).close();
+ }
+
+ @Test
+ void assertCheckWithoutResultMetadata() {
+ assertDoesNotThrow(() -> checker.check(createExecutionUnits("ds_0",
"ds_1"), mock(PreparedStatement.class), "UPDATE foo SET value = 1"));
+ }
+
+ private Collection<ExecutionUnit> createExecutionUnits(final String...
storageUnitNames) {
+ ExecutionUnit[] result = new ExecutionUnit[storageUnitNames.length];
+ for (int i = 0; i < storageUnitNames.length; i++) {
+ result[i] = new ExecutionUnit(storageUnitNames[i], new
SQLUnit("SELECT value", Collections.emptyList()));
+ }
+ return Arrays.asList(result);
+ }
+
+ private ResultSetMetaData createMetaData(final int columnType) throws
SQLException {
+ ResultSetMetaData result = mock(ResultSetMetaData.class);
+ when(result.getColumnCount()).thenReturn(1);
+ when(result.getColumnType(1)).thenReturn(columnType);
+ return result;
+ }
+}
diff --git
a/proxy/backend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/backend/postgresql/response/header/query/PostgreSQLQueryHeaderBuilderTest.java
b/proxy/backend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/backend/postgresql/response/header/query/PostgreSQLQueryHeaderBuilderTest.java
index a9cb26074f7..f8f82b5eb5d 100644
---
a/proxy/backend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/backend/postgresql/response/header/query/PostgreSQLQueryHeaderBuilderTest.java
+++
b/proxy/backend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/backend/postgresql/response/header/query/PostgreSQLQueryHeaderBuilderTest.java
@@ -17,16 +17,25 @@
package
org.apache.shardingsphere.proxy.backend.postgresql.response.header.query;
+import
org.apache.shardingsphere.database.protocol.postgresql.type.PostgreSQLColumnTypeOIDLoader;
import
org.apache.shardingsphere.driver.jdbc.core.resultset.ShardingSphereResultSetMetaData;
import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryHeader;
import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import java.sql.Connection;
+import java.sql.ResultSet;
import java.sql.SQLException;
+import java.sql.Statement;
import java.sql.Types;
+import java.util.Optional;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
class PostgreSQLQueryHeaderBuilderTest {
@@ -45,4 +54,32 @@ class PostgreSQLQueryHeaderBuilderTest {
assertThat(actual.getColumnTypeName(), is("int"));
assertThat(actual.getColumnLength(), is(11));
}
+
+ @Test
+ void assertBuildPostgreSQLCompositeQueryHeader() throws SQLException {
+ int columnIndex = 1;
+ ShardingSphereResultSetMetaData resultSetMetaData =
mock(ShardingSphereResultSetMetaData.class);
+
when(resultSetMetaData.getColumnType(columnIndex)).thenReturn(Types.STRUCT);
+
when(resultSetMetaData.getColumnTypeName(columnIndex)).thenReturn("record_type");
+ ResultSet resultSet = mock(ResultSet.class);
+ Statement statement = mock(Statement.class);
+ Connection connection = mock(Connection.class);
+ when(resultSet.getStatement()).thenReturn(statement);
+ when(statement.getConnection()).thenReturn(connection);
+ try (MockedStatic<PostgreSQLColumnTypeOIDLoader> loader =
mockStatic(PostgreSQLColumnTypeOIDLoader.class)) {
+ loader.when(() ->
PostgreSQLColumnTypeOIDLoader.findTypeOID(connection,
"record_type")).thenReturn(Optional.of(2249));
+ QueryHeader actual = new
PostgreSQLQueryHeaderBuilder().build(resultSetMetaData, resultSet, null, null,
"record", columnIndex);
+
assertThat(actual.getProtocolAttributes().get(PostgreSQLQueryHeaderBuilder.TYPE_OID),
is(2249));
+ }
+ }
+
+ @Test
+ void assertBuildPostgreSQLQueryHeaderFromResultSet() throws SQLException {
+ ShardingSphereResultSetMetaData resultSetMetaData =
mock(ShardingSphereResultSetMetaData.class);
+ when(resultSetMetaData.getColumnType(1)).thenReturn(Types.INTEGER);
+ ResultSet resultSet = mock(ResultSet.class);
+ QueryHeader actual = new
PostgreSQLQueryHeaderBuilder().build(resultSetMetaData, resultSet, null, null,
"id", 1);
+ assertTrue(actual.getProtocolAttributes().isEmpty());
+ verifyNoInteractions(resultSet);
+ }
}
diff --git
a/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/binary/prepare/MySQLProjectionMetadataResolver.java
b/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/binary/prepare/MySQLProjectionMetadataResolver.java
index ebd0a75fc32..edbe661f07c 100644
---
a/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/binary/prepare/MySQLProjectionMetadataResolver.java
+++
b/proxy/frontend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/frontend/mysql/command/query/binary/prepare/MySQLProjectionMetadataResolver.java
@@ -40,7 +40,6 @@ import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashSet;
/**
@@ -78,8 +77,7 @@ public final class MySQLProjectionMetadataResolver {
QueryHeaderBuilderEngine queryHeaderBuilderEngine = new
QueryHeaderBuilderEngine(database.getProtocolType());
Collection<MySQLPacket> result = new
ArrayList<>(selectStatementContext.getProjectionsContext().getExpandProjections().size());
for (int columnIndex = 1; columnIndex <=
selectStatementContext.getProjectionsContext().getExpandProjections().size();
columnIndex++) {
- QueryHeader queryHeader =
- queryHeaderBuilderEngine.build(selectStatementContext,
resultSetMetaData, database, Collections.singleton(database), columnIndex);
+ QueryHeader queryHeader =
queryHeaderBuilderEngine.build(selectStatementContext, resultSetMetaData,
database, columnIndex);
result.add(createMySQLColumnDefinition41Packet(queryHeader,
characterSet));
}
return result;
diff --git
a/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/Portal.java
b/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/Portal.java
index 504c97bb980..e4542e185bc 100644
---
a/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/Portal.java
+++
b/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/Portal.java
@@ -49,6 +49,7 @@ import
org.apache.shardingsphere.proxy.backend.response.header.ResponseHeader;
import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryHeader;
import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryResponseHeader;
import
org.apache.shardingsphere.proxy.backend.response.header.update.UpdateResponseHeader;
+import
org.apache.shardingsphere.proxy.backend.postgresql.response.header.query.PostgreSQLQueryHeaderBuilder;
import
org.apache.shardingsphere.proxy.frontend.postgresql.command.query.PostgreSQLCommand;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.dal.VariableAssignSegment;
import
org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
@@ -136,7 +137,11 @@ public final class Portal {
int columnIndex = 0;
for (QueryHeader each : queryResponseHeader.getQueryHeaders()) {
PostgreSQLValueFormat valueFormat =
determineValueFormat(columnIndex);
- result.add(new PostgreSQLColumnDescription(each.getColumnLabel(),
++columnIndex, each.getColumnType(), each.getColumnLength(),
each.getColumnTypeName(), valueFormat.getCode()));
+ int currentColumnIndex = ++columnIndex;
+ Integer typeOID = (Integer)
each.getProtocolAttributes().get(PostgreSQLQueryHeaderBuilder.TYPE_OID);
+ result.add(PostgreSQLValueFormat.TEXT == valueFormat && null !=
typeOID
+ ? new PostgreSQLColumnDescription(each.getColumnLabel(),
currentColumnIndex, typeOID, each.getColumnLength(), valueFormat.getCode())
+ : new PostgreSQLColumnDescription(each.getColumnLabel(),
currentColumnIndex, each.getColumnType(), each.getColumnLength(),
each.getColumnTypeName(), valueFormat.getCode()));
}
return result;
}
diff --git
a/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/PostgreSQLPreparedStatementMetadataFactory.java
b/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/PostgreSQLPreparedStatementMetadataFactory.java
index c0709b3319b..406f9536813 100644
---
a/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/PostgreSQLPreparedStatementMetadataFactory.java
+++
b/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/PostgreSQLPreparedStatementMetadataFactory.java
@@ -19,18 +19,22 @@ package
org.apache.shardingsphere.proxy.frontend.postgresql.command.query.extend
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
+import
org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
import org.apache.shardingsphere.infra.binder.context.aware.ParameterAware;
import
org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
import org.apache.shardingsphere.infra.binder.engine.SQLBindEngine;
import org.apache.shardingsphere.infra.connection.kernel.KernelProcessor;
import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
+import
org.apache.shardingsphere.infra.exception.external.sql.ShardingSphereSQLException;
import
org.apache.shardingsphere.infra.exception.kernel.metadata.PreparedStatementMetadataResolutionException;
import org.apache.shardingsphere.infra.executor.sql.context.ExecutionContext;
import org.apache.shardingsphere.infra.executor.sql.context.ExecutionUnit;
import
org.apache.shardingsphere.infra.executor.sql.execute.engine.ConnectionMode;
import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
import org.apache.shardingsphere.infra.session.query.QueryContext;
+import org.apache.shardingsphere.infra.util.close.QuietlyCloser;
import
org.apache.shardingsphere.proxy.backend.connector.ProxyDatabaseConnectionManager;
+import
org.apache.shardingsphere.proxy.backend.connector.jdbc.executor.DialectJDBCResultMetadataChecker;
import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
@@ -38,6 +42,7 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
+import java.util.Optional;
/**
* Metadata factory for PostgreSQL prepared statements.
@@ -72,6 +77,20 @@ public final class
PostgreSQLPreparedStatementMetadataFactory {
databaseConnectionManager.getConnections(connectionSession.getUsedDatabaseName(),
executionUnit.getDataSourceName(), 0, 1, ConnectionMode.CONNECTION_STRICTLY);
ShardingSpherePreconditions.checkNotEmpty(connections,
() -> new PreparedStatementMetadataResolutionException("no
backend connection was acquired"));
- return
connections.iterator().next().prepareStatement(executionUnit.getSqlUnit().getSql());
+ String sql = executionUnit.getSqlUnit().getSql();
+ PreparedStatement result =
connections.iterator().next().prepareStatement(sql);
+ try {
+ if (executionContext.getExecutionUnits().size() > 1) {
+ Optional<DialectJDBCResultMetadataChecker> checker =
+
DatabaseTypedSPILoader.findService(DialectJDBCResultMetadataChecker.class,
connectionSession.getProtocolType());
+ if (checker.isPresent()) {
+ checker.get().check(executionContext.getExecutionUnits(),
result, sql);
+ }
+ }
+ } catch (final SQLException | ShardingSphereSQLException ex) {
+ QuietlyCloser.close(result);
+ throw ex;
+ }
+ return result;
}
}
diff --git
a/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/describe/PostgreSQLComDescribeExecutor.java
b/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/describe/PostgreSQLComDescribeExecutor.java
index 986c476a9d6..411fc417070 100644
---
a/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/describe/PostgreSQLComDescribeExecutor.java
+++
b/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/describe/PostgreSQLComDescribeExecutor.java
@@ -21,10 +21,11 @@ import lombok.RequiredArgsConstructor;
import
org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
import
org.apache.shardingsphere.database.exception.core.exception.syntax.column.ColumnNotFoundException;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
-import
org.apache.shardingsphere.database.protocol.postgresql.packet.PostgreSQLPacket;
+import
org.apache.shardingsphere.database.protocol.postgresql.constant.PostgreSQLValueFormat;
import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query.PostgreSQLColumnDescription;
import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query.PostgreSQLNoDataPacket;
import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query.PostgreSQLRowDescriptionPacket;
+import
org.apache.shardingsphere.database.protocol.postgresql.type.PostgreSQLColumnTypeOIDLoader;
import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query.extended.PostgreSQLBinaryColumnType;
import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query.extended.describe.PostgreSQLComDescribePacket;
import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
@@ -60,9 +61,9 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
+import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -111,7 +112,7 @@ public final class PostgreSQLComDescribeExecutor implements
CommandExecutor {
}
}
- private void describeInsertStatementByDatabaseMetaData(final
PostgreSQLServerPreparedStatement preparedStatement) {
+ private void describeInsertStatementByDatabaseMetaData(final
PostgreSQLServerPreparedStatement preparedStatement) throws SQLException {
InsertStatement insertStatement = (InsertStatement)
preparedStatement.getSqlStatementContext().getSqlStatement();
Collection<Integer> unspecifiedTypeParameterIndexes =
getUnspecifiedTypeParameterIndexes(preparedStatement);
Optional<ReturningSegment> returningSegment =
insertStatement.getReturning();
@@ -121,7 +122,9 @@ public final class PostgreSQLComDescribeExecutor implements
CommandExecutor {
String logicTableName = insertStatement.getTable().map(optional ->
optional.getTableName().getIdentifier().getValue()).orElse("");
ShardingSphereTable table =
getTableFromMetaData(connectionSession.getUsedDatabaseName(), insertStatement,
logicTableName);
List<ShardingSphereIdentifier> columnNamesOfInsert =
getColumnNamesOfInsertStatement(insertStatement, table);
-
preparedStatement.setRowDescription(returningSegment.<PostgreSQLPacket>map(returning
-> describeReturning(returning,
table)).orElseGet(PostgreSQLNoDataPacket::getInstance));
+ preparedStatement.setRowDescription(returningSegment.isPresent()
+ ? describeReturning(returningSegment.get(), table,
preparedStatement)
+ : PostgreSQLNoDataPacket.getInstance());
int parameterMarkerIndex = 0;
for (InsertValuesSegment each : insertStatement.getValues()) {
for (int i = 0; i < each.getValues().size(); i++) {
@@ -164,8 +167,9 @@ public final class PostgreSQLComDescribeExecutor implements
CommandExecutor {
: insertStatement.getColumns().stream().map(each -> new
ShardingSphereIdentifier(each.getIdentifier().getValue())).collect(Collectors.toList());
}
- private PostgreSQLRowDescriptionPacket describeReturning(final
ReturningSegment returningSegment, final ShardingSphereTable table) {
- Collection<PostgreSQLColumnDescription> result = new LinkedList<>();
+ private PostgreSQLRowDescriptionPacket describeReturning(final
ReturningSegment returningSegment, final ShardingSphereTable table,
+ final
PostgreSQLServerPreparedStatement preparedStatement) throws SQLException {
+ List<PostgreSQLColumnDescription> result = new ArrayList<>();
for (ProjectionSegment each :
returningSegment.getProjections().getProjections()) {
if (each instanceof ShorthandProjectionSegment) {
table.getAllColumns().stream()
@@ -182,6 +186,7 @@ public final class PostgreSQLComDescribeExecutor implements
CommandExecutor {
result.add(convertExpressionToDescription((ExpressionProjectionSegment) each));
}
}
+ applyColumnTypeOIDs(result, loadColumnTypeOIDs(preparedStatement,
result.size()));
return new PostgreSQLRowDescriptionPacket(result);
}
@@ -223,6 +228,15 @@ public final class PostgreSQLComDescribeExecutor
implements CommandExecutor {
}
}
+ private Map<Integer, Integer> loadColumnTypeOIDs(final
PostgreSQLServerPreparedStatement preparedStatement, final int
expectedColumnCount) throws SQLException {
+ try (PreparedStatement actualPreparedStatement =
PostgreSQLPreparedStatementMetadataFactory.load(connectionSession,
preparedStatement, Collections.emptyList())) {
+ ResultSetMetaData metaData = actualPreparedStatement.getMetaData();
+ return null == metaData || expectedColumnCount !=
metaData.getColumnCount()
+ ? Collections.emptyMap()
+ :
PostgreSQLColumnTypeOIDLoader.load(actualPreparedStatement.getConnection(),
metaData);
+ }
+ }
+
private void tryDescribePreparedStatementByJDBC(final
PostgreSQLServerPreparedStatement logicPreparedStatement) throws SQLException {
try (PreparedStatement actualPreparedStatement =
PostgreSQLPreparedStatementMetadataFactory.load(connectionSession,
logicPreparedStatement, Collections.emptyList())) {
PostgreSQLPreparedStatementParameterTypeResolver.resolveParameterTypes(logicPreparedStatement,
actualPreparedStatement);
@@ -239,14 +253,32 @@ public final class PostgreSQLComDescribeExecutor
implements CommandExecutor {
logicPreparedStatement.setRowDescription(PostgreSQLNoDataPacket.getInstance());
return;
}
+ Map<Integer, Integer> columnTypeOIDs =
PostgreSQLColumnTypeOIDLoader.load(actualPreparedStatement.getConnection(),
resultSetMetaData);
List<PostgreSQLColumnDescription> columnDescriptions = new
ArrayList<>(resultSetMetaData.getColumnCount());
for (int columnIndex = 1; columnIndex <=
resultSetMetaData.getColumnCount(); columnIndex++) {
String columnName = resultSetMetaData.getColumnName(columnIndex);
int columnType = resultSetMetaData.getColumnType(columnIndex);
int columnLength =
resultSetMetaData.getColumnDisplaySize(columnIndex);
String columnTypeName =
resultSetMetaData.getColumnTypeName(columnIndex);
- columnDescriptions.add(new PostgreSQLColumnDescription(columnName,
columnIndex, columnType, columnLength, columnTypeName));
+ Integer typeOID = columnTypeOIDs.get(columnIndex);
+ columnDescriptions.add(null != typeOID
+ ? new PostgreSQLColumnDescription(columnName, columnIndex,
typeOID, columnLength, PostgreSQLValueFormat.TEXT.getCode())
+ : new PostgreSQLColumnDescription(columnName, columnIndex,
columnType, columnLength, columnTypeName));
}
logicPreparedStatement.setRowDescription(new
PostgreSQLRowDescriptionPacket(columnDescriptions));
}
+
+ private void applyColumnTypeOIDs(final List<PostgreSQLColumnDescription>
columnDescriptions, final Map<Integer, Integer> columnTypeOIDs) {
+ if (columnTypeOIDs.isEmpty()) {
+ return;
+ }
+ for (int columnIndex = 1; columnIndex <= columnDescriptions.size();
columnIndex++) {
+ Integer typeOID = columnTypeOIDs.get(columnIndex);
+ if (null != typeOID) {
+ PostgreSQLColumnDescription columnDescription =
columnDescriptions.get(columnIndex - 1);
+ columnDescriptions.set(columnIndex - 1, new
PostgreSQLColumnDescription(
+ columnDescription.getColumnName(),
columnDescription.getColumnIndex(), typeOID,
columnDescription.getColumnLength(), columnDescription.getDataFormat()));
+ }
+ }
+ }
}
diff --git
a/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/simple/PostgreSQLComQueryExecutor.java
b/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/simple/PostgreSQLComQueryExecutor.java
index 90eb68e68f1..1dfef9fe909 100644
---
a/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/simple/PostgreSQLComQueryExecutor.java
+++
b/proxy/frontend/dialect/postgresql/src/main/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/simple/PostgreSQLComQueryExecutor.java
@@ -21,6 +21,7 @@ import lombok.Getter;
import
org.apache.shardingsphere.database.connector.core.metadata.database.enums.QuoteCharacter;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
+import
org.apache.shardingsphere.database.protocol.postgresql.constant.PostgreSQLValueFormat;
import
org.apache.shardingsphere.database.protocol.postgresql.packet.PostgreSQLPacket;
import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query.PostgreSQLColumnDescription;
import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query.PostgreSQLDataRowPacket;
@@ -38,6 +39,7 @@ import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryHeader
import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryResponseHeader;
import
org.apache.shardingsphere.proxy.backend.response.header.update.UpdateResponseHeader;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
+import
org.apache.shardingsphere.proxy.backend.postgresql.response.header.query.PostgreSQLQueryHeaderBuilder;
import
org.apache.shardingsphere.proxy.frontend.command.executor.QueryCommandExecutor;
import org.apache.shardingsphere.proxy.frontend.command.executor.ResponseType;
import
org.apache.shardingsphere.proxy.frontend.postgresql.command.PortalContext;
@@ -98,7 +100,11 @@ public final class PostgreSQLComQueryExecutor implements
QueryCommandExecutor {
Collection<PostgreSQLColumnDescription> result = new LinkedList<>();
int columnIndex = 0;
for (QueryHeader each : queryResponseHeader.getQueryHeaders()) {
- result.add(new PostgreSQLColumnDescription(each.getColumnLabel(),
++columnIndex, each.getColumnType(), each.getColumnLength(),
each.getColumnTypeName()));
+ int currentColumnIndex = ++columnIndex;
+ Integer typeOID = (Integer)
each.getProtocolAttributes().get(PostgreSQLQueryHeaderBuilder.TYPE_OID);
+ result.add(null == typeOID
+ ? new PostgreSQLColumnDescription(each.getColumnLabel(),
currentColumnIndex, each.getColumnType(), each.getColumnLength(),
each.getColumnTypeName())
+ : new PostgreSQLColumnDescription(each.getColumnLabel(),
currentColumnIndex, typeOID, each.getColumnLength(),
PostgreSQLValueFormat.TEXT.getCode()));
}
return result;
}
diff --git
a/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/PortalTest.java
b/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/PortalTest.java
index 1aa91f84c6a..b026d5f3b06 100644
---
a/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/PortalTest.java
+++
b/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/PortalTest.java
@@ -52,6 +52,7 @@ import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryHeader
import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryResponseHeader;
import
org.apache.shardingsphere.proxy.backend.response.header.update.UpdateResponseHeader;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
+import
org.apache.shardingsphere.proxy.backend.postgresql.response.header.query.PostgreSQLQueryHeaderBuilder;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.dal.VariableAssignSegment;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.dal.VariableSegment;
import
org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
@@ -136,9 +137,10 @@ class PortalTest {
@Test
void assertExecuteSelectStatementAndReturnAllRows() throws SQLException,
ReflectiveOperationException {
QueryResponseHeader responseHeader = mock(QueryResponseHeader.class);
- QueryHeader queryHeader = new QueryHeader("schema", "table",
"columnLabel", "columnName", Types.VARCHAR, "columnTypeName", 0, 0, false,
false, false, false);
- QueryHeader intColumnQueryHeader = new QueryHeader("schema", "table",
"columnLabel", "columnName", Types.INTEGER, "columnTypeName", 0, 0, false,
false, false, false);
-
when(responseHeader.getQueryHeaders()).thenReturn(Arrays.asList(queryHeader,
intColumnQueryHeader));
+ QueryHeader queryHeader = new QueryHeader("schema", "table",
"columnLabel", "columnName", Types.STRUCT, "record_type", 0, 0, false, false,
false, false,
+
Collections.singletonMap(PostgreSQLQueryHeaderBuilder.TYPE_OID, 2249));
+ QueryHeader binaryQueryHeader = new QueryHeader("schema", "table",
"columnLabel", "columnName", Types.STRUCT, "record_type", 0, 0, false, false,
false, false);
+
when(responseHeader.getQueryHeaders()).thenReturn(Arrays.asList(queryHeader,
binaryQueryHeader));
when(proxyBackendHandler.execute()).thenReturn(responseHeader);
when(proxyBackendHandler.next()).thenReturn(true, true, false);
when(proxyBackendHandler.getRowData()).thenReturn(new
QueryResponseRow(Collections.singletonList(new QueryResponseCell(Types.INTEGER,
0))),
@@ -153,9 +155,11 @@ class PortalTest {
.get(PostgreSQLRowDescriptionPacket.class.getDeclaredField("columnDescriptions"),
portalDescription);
Iterator<PostgreSQLColumnDescription> columnDescriptionIterator =
columnDescriptions.iterator();
PostgreSQLColumnDescription textColumnDescription =
columnDescriptionIterator.next();
- PostgreSQLColumnDescription intColumnDescription =
columnDescriptionIterator.next();
+ PostgreSQLColumnDescription binaryColumnDescription =
columnDescriptionIterator.next();
+ assertThat(textColumnDescription.getTypeOID(), is(2249));
assertThat(textColumnDescription.getDataFormat(),
is(PostgreSQLValueFormat.TEXT.getCode()));
- assertThat(intColumnDescription.getDataFormat(),
is(PostgreSQLValueFormat.BINARY.getCode()));
+ assertThat(binaryColumnDescription.getTypeOID(),
is(PostgreSQLBinaryColumnType.VARCHAR.getValue()));
+ assertThat(binaryColumnDescription.getDataFormat(),
is(PostgreSQLValueFormat.BINARY.getCode()));
List<DatabasePacket> actualPackets = portal.execute(0);
assertThat(actualPackets.size(), is(3));
assertThat(actualPackets.get(0), isA(PostgreSQLDataRowPacket.class));
diff --git
a/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/PostgreSQLPreparedStatementMetadataFactoryTest.java
b/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/PostgreSQLPreparedStatementMetadataFactoryTest.java
index 6881558cdbc..b2c47e89fd4 100644
---
a/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/PostgreSQLPreparedStatementMetadataFactoryTest.java
+++
b/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/PostgreSQLPreparedStatementMetadataFactoryTest.java
@@ -17,6 +17,7 @@
package
org.apache.shardingsphere.proxy.frontend.postgresql.command.query.extended;
+import
org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query.extended.PostgreSQLBinaryColumnType;
import
org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
@@ -24,8 +25,11 @@ import
org.apache.shardingsphere.infra.binder.context.statement.type.dml.SelectS
import org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
import org.apache.shardingsphere.infra.connection.kernel.KernelProcessor;
import
org.apache.shardingsphere.infra.exception.external.sql.ShardingSphereSQLException;
+import
org.apache.shardingsphere.infra.exception.generic.UnsupportedSQLOperationException;
import
org.apache.shardingsphere.infra.exception.kernel.metadata.PreparedStatementMetadataResolutionException;
import org.apache.shardingsphere.infra.executor.sql.context.ExecutionContext;
+import org.apache.shardingsphere.infra.executor.sql.context.ExecutionUnit;
+import org.apache.shardingsphere.infra.executor.sql.context.SQLUnit;
import org.apache.shardingsphere.infra.hint.HintValueContext;
import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
import
org.apache.shardingsphere.infra.metadata.database.resource.unit.StorageUnit;
@@ -38,6 +42,7 @@ import
org.apache.shardingsphere.infra.session.connection.ConnectionContext;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.mode.manager.ContextManager;
import
org.apache.shardingsphere.proxy.backend.connector.ProxyDatabaseConnectionManager;
+import
org.apache.shardingsphere.proxy.backend.connector.jdbc.executor.DialectJDBCResultMetadataChecker;
import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
import org.apache.shardingsphere.sql.parser.engine.api.CacheOption;
@@ -51,13 +56,16 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import java.util.Properties;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -68,9 +76,13 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(AutoMockExtension.class)
@@ -115,11 +127,35 @@ class PostgreSQLPreparedStatementMetadataFactoryTest {
}
}
+ @Test
+ void assertLoadWithMetadataCheckerException() throws SQLException {
+ PreparedStatement expected = prepareJDBCBackendConnection(null);
+ PostgreSQLServerPreparedStatement preparedStatement =
createPreparedStatement(true);
+ ExecutionContext executionContext = mock(ExecutionContext.class);
+ List<ExecutionUnit> executionUnits = Arrays.asList(
+ new ExecutionUnit("ds_0", new SQLUnit("SELECT id FROM foo_tbl
WHERE id=?", PARAMETERS)),
+ new ExecutionUnit("ds_1", new SQLUnit("SELECT id FROM foo_tbl
WHERE id=?", PARAMETERS)));
+ when(executionContext.getExecutionUnits()).thenReturn(executionUnits);
+ DialectJDBCResultMetadataChecker checker =
mock(DialectJDBCResultMetadataChecker.class);
+ UnsupportedSQLOperationException expectedException = new
UnsupportedSQLOperationException("expected");
+ doThrow(expectedException).when(checker).check(executionUnits,
expected, "SELECT id FROM foo_tbl WHERE id=?");
+ try (
+ MockedConstruction<KernelProcessor> ignored =
mockConstruction(KernelProcessor.class,
+ (mock, context) ->
when(mock.generateExecutionContext(any(), any(),
any())).thenReturn(executionContext));
+ MockedStatic<DatabaseTypedSPILoader> spiLoader =
mockStatic(DatabaseTypedSPILoader.class, CALLS_REAL_METHODS)) {
+ spiLoader.when(() ->
DatabaseTypedSPILoader.findService(DialectJDBCResultMetadataChecker.class,
databaseType)).thenReturn(Optional.of(checker));
+ assertThat(assertThrows(UnsupportedSQLOperationException.class,
+ () ->
PostgreSQLPreparedStatementMetadataFactory.load(connectionSession,
preparedStatement, PARAMETERS)), is(expectedException));
+ verify(expected).close();
+ }
+ }
+
private PostgreSQLServerPreparedStatement createPreparedStatement(final
boolean withUsedDatabaseName) {
SQLStatement sqlStatement = sqlParserEngine.parse("SELECT id FROM
foo_tbl WHERE id=?", false);
SQLStatementContext sqlStatementContext =
mock(SelectStatementContext.class);
when(sqlStatementContext.getSqlStatement()).thenReturn(sqlStatement);
when(connectionSession.getCurrentDatabaseName()).thenReturn("postgres");
+
lenient().when(connectionSession.getProtocolType()).thenReturn(databaseType);
if (withUsedDatabaseName) {
when(connectionSession.getUsedDatabaseName()).thenReturn("postgres");
}
diff --git
a/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/describe/PostgreSQLComDescribeExecutorTest.java
b/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/describe/PostgreSQLComDescribeExecutorTest.java
index a97d31e2b02..e08465f1b09 100644
---
a/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/describe/PostgreSQLComDescribeExecutorTest.java
+++
b/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/extended/describe/PostgreSQLComDescribeExecutorTest.java
@@ -21,6 +21,7 @@ import lombok.SneakyThrows;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import
org.apache.shardingsphere.database.exception.core.exception.syntax.column.ColumnNotFoundException;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
+import
org.apache.shardingsphere.database.protocol.postgresql.constant.PostgreSQLValueFormat;
import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query.PostgreSQLColumnDescription;
import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query.PostgreSQLNoDataPacket;
import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query.PostgreSQLParameterDescriptionPacket;
@@ -28,6 +29,7 @@ import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.que
import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query.extended.PostgreSQLBinaryColumnType;
import
org.apache.shardingsphere.database.protocol.postgresql.packet.command.query.extended.describe.PostgreSQLComDescribePacket;
import
org.apache.shardingsphere.database.protocol.postgresql.payload.PostgreSQLPacketPayload;
+import
org.apache.shardingsphere.database.protocol.postgresql.type.PostgreSQLColumnTypeOIDLoader;
import
org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
import
org.apache.shardingsphere.infra.binder.context.statement.type.dml.InsertStatementContext;
import
org.apache.shardingsphere.infra.binder.context.statement.type.dml.SelectStatementContext;
@@ -60,6 +62,7 @@ import
org.apache.shardingsphere.sqltranslator.rule.SQLTranslatorRule;
import
org.apache.shardingsphere.sqltranslator.rule.builder.DefaultSQLTranslatorRuleConfigurationBuilder;
import
org.apache.shardingsphere.test.infra.framework.extension.mock.AutoMockExtension;
import
org.apache.shardingsphere.test.infra.framework.extension.mock.StaticMockSettings;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
@@ -81,6 +84,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -106,7 +110,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(AutoMockExtension.class)
-@StaticMockSettings(ProxyContext.class)
+@StaticMockSettings({ProxyContext.class, PostgreSQLColumnTypeOIDLoader.class})
@MockitoSettings(strictness = Strictness.LENIENT)
class PostgreSQLComDescribeExecutorTest {
@@ -127,9 +131,18 @@ class PostgreSQLComDescribeExecutorTest {
@Mock
private ConnectionSession connectionSession;
+ private Map<Integer, Integer> columnTypeOIDs;
+
@InjectMocks
private PostgreSQLComDescribeExecutor executor;
+ @BeforeEach
+ void setUp() throws SQLException {
+ when(connectionSession.getProtocolType()).thenReturn(DATABASE_TYPE);
+ columnTypeOIDs = Collections.emptyMap();
+ when(PostgreSQLColumnTypeOIDLoader.load(any(Connection.class),
any(ResultSetMetaData.class))).thenAnswer(ignored -> columnTypeOIDs);
+ }
+
@Test
void assertDescribePortal() throws SQLException {
when(packet.getType()).thenReturn('P');
@@ -254,7 +267,6 @@ class PostgreSQLComDescribeExecutorTest {
when(packet.getName()).thenReturn(statementId);
String sql = "INSERT INTO public.t_small (col1, col2) VALUES (?, ?)
RETURNING *, col1 + col2 expr_sum";
SQLStatement sqlStatement = SQL_PARSER_ENGINE.parse(sql, false);
- List<PostgreSQLBinaryColumnType> parameterTypes =
Arrays.asList(PostgreSQLBinaryColumnType.INT4,
PostgreSQLBinaryColumnType.UNSPECIFIED);
SQLStatementContext sqlStatementContext =
mock(InsertStatementContext.class);
when(sqlStatementContext.getSqlStatement()).thenReturn(sqlStatement);
ShardingSphereTable table = new ShardingSphereTable("t_small",
@@ -264,6 +276,8 @@ class PostgreSQLComDescribeExecutorTest {
Collections.emptyList(), Collections.emptyList());
ContextManager contextManager = mockContextManager(table);
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+ prepareJDBCBackendConnectionForResultMetaData(sql, 3);
+ List<PostgreSQLBinaryColumnType> parameterTypes =
Arrays.asList(PostgreSQLBinaryColumnType.INT4,
PostgreSQLBinaryColumnType.UNSPECIFIED);
List<Integer> parameterIndexes = IntStream.range(0,
sqlStatement.getParameterCount()).boxed().collect(Collectors.toList());
connectionSession.getServerPreparedStatementRegistry().addPreparedStatement(
statementId, new PostgreSQLServerPreparedStatement(sql,
sqlStatementContext, new HintValueContext(), parameterTypes, parameterIndexes));
@@ -377,6 +391,12 @@ class PostgreSQLComDescribeExecutorTest {
when(sqlStatementContext.getSqlStatement()).thenReturn(sqlStatement);
ContextManager contextManager = mockContextManager();
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+ prepareJDBCBackendConnectionForResultMetaData(sql,
expectedColumns.size());
+ if ("returning complex columns".equals(testName)) {
+ columnTypeOIDs = new HashMap<>(2, 1F);
+ columnTypeOIDs.put(12, 2249);
+ columnTypeOIDs.put(13, 2249);
+ }
List<Integer> parameterIndexes = IntStream.range(0,
sqlStatement.getParameterCount()).boxed().collect(Collectors.toList());
connectionSession.getServerPreparedStatementRegistry().addPreparedStatement(
statementId, new PostgreSQLServerPreparedStatement(sql,
sqlStatementContext, new HintValueContext(), parameterTypes, parameterIndexes));
@@ -413,13 +433,14 @@ class PostgreSQLComDescribeExecutorTest {
SQLStatementContext sqlStatementContext =
mock(SelectStatementContext.class);
when(sqlStatementContext.getSqlStatement()).thenReturn(sqlStatement);
prepareJDBCBackendConnection(sql);
- List<PostgreSQLBinaryColumnType> parameterTypes = new
ArrayList<>(Collections.singleton(PostgreSQLBinaryColumnType.UNSPECIFIED));
ContextManager contextManager = mockContextManager();
when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
- List<Integer> parameterIndexes = IntStream.range(0,
sqlStatement.getParameterCount()).boxed().collect(Collectors.toList());
ConnectionContext connectionContext = mock(ConnectionContext.class);
when(connectionContext.getCurrentDatabaseName()).thenReturn(Optional.of(DATABASE_NAME));
when(connectionSession.getConnectionContext()).thenReturn(connectionContext);
+ columnTypeOIDs = Collections.singletonMap(1, 2249);
+ List<PostgreSQLBinaryColumnType> parameterTypes = new
ArrayList<>(Collections.singleton(PostgreSQLBinaryColumnType.UNSPECIFIED));
+ List<Integer> parameterIndexes = IntStream.range(0,
sqlStatement.getParameterCount()).boxed().collect(Collectors.toList());
connectionSession.getServerPreparedStatementRegistry().addPreparedStatement(
statementId, new PostgreSQLServerPreparedStatement(sql,
sqlStatementContext, new HintValueContext(), parameterTypes, parameterIndexes));
Collection<DatabasePacket> actual = executor.execute();
@@ -434,7 +455,7 @@ class PostgreSQLComDescribeExecutorTest {
PostgreSQLRowDescriptionPacket actualRowDescription =
(PostgreSQLRowDescriptionPacket) actualPacketsIterator.next();
List<PostgreSQLColumnDescription> actualColumnDescriptions =
getColumnDescriptions(actualRowDescription);
List<PostgreSQLColumnDescription> expectedColumnDescriptions =
Arrays.asList(
- new PostgreSQLColumnDescription("id", 1, Types.INTEGER, 11,
"int4"),
+ new PostgreSQLColumnDescription("id", 1, 2249, 11,
PostgreSQLValueFormat.TEXT.getCode()),
new PostgreSQLColumnDescription("k", 2, Types.INTEGER, 11,
"int4"),
new PostgreSQLColumnDescription("c", 3, Types.CHAR, 60,
"int4"),
new PostgreSQLColumnDescription("pad", 4, Types.CHAR, 120,
"int4"));
@@ -644,6 +665,19 @@ class PostgreSQLComDescribeExecutorTest {
when(connectionSession.getDatabaseConnectionManager()).thenReturn(databaseConnectionManager);
}
+ private void prepareJDBCBackendConnectionForResultMetaData(final String
sql, final int columnCount) throws SQLException {
+ ProxyDatabaseConnectionManager databaseConnectionManager =
mock(ProxyDatabaseConnectionManager.class);
+ Connection connection = mock(Connection.class);
+ PreparedStatement preparedStatement = mock(PreparedStatement.class);
+ ResultSetMetaData metaData = mock(ResultSetMetaData.class);
+ when(metaData.getColumnCount()).thenReturn(columnCount);
+ when(preparedStatement.getMetaData()).thenReturn(metaData);
+ when(preparedStatement.getConnection()).thenReturn(connection);
+ when(connection.prepareStatement(sql)).thenReturn(preparedStatement);
+ when(databaseConnectionManager.getConnections(any(),
nullable(String.class), anyInt(), anyInt(),
any(ConnectionMode.class))).thenReturn(Collections.singletonList(connection));
+
when(connectionSession.getDatabaseConnectionManager()).thenReturn(databaseConnectionManager);
+ }
+
private void prepareJDBCBackendConnectionWithParamTypes(final String sql,
final int[] paramTypes, final String[] paramTypeNames) throws SQLException {
ParameterMetaData parameterMetaData = mock(ParameterMetaData.class);
for (int i = 0; i < paramTypes.length; i++) {
@@ -738,7 +772,7 @@ class PostgreSQLComDescribeExecutorTest {
expectedColumn("k", Types.INTEGER, 4, "int4"),
expectedColumn("c", Types.CHAR, -1, "char"),
expectedColumn("pad", Types.CHAR, -1, "char"),
- expectedColumn("t_order", Types.VARCHAR, -1, "varchar"),
- expectedColumn("alias_t_order", Types.VARCHAR, -1, "varchar"));
+ new PostgreSQLColumnDescription("t_order", 0, 2249, -1,
PostgreSQLValueFormat.TEXT.getCode()),
+ new PostgreSQLColumnDescription("alias_t_order", 0, 2249, -1,
PostgreSQLValueFormat.TEXT.getCode()));
}
}
diff --git
a/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/simple/PostgreSQLComQueryExecutorTest.java
b/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/simple/PostgreSQLComQueryExecutorTest.java
index 00cf2bb5984..2c64ed34715 100644
---
a/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/simple/PostgreSQLComQueryExecutorTest.java
+++
b/proxy/frontend/dialect/postgresql/src/test/java/org/apache/shardingsphere/proxy/frontend/postgresql/command/query/simple/PostgreSQLComQueryExecutorTest.java
@@ -38,6 +38,7 @@ import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryHeader
import
org.apache.shardingsphere.proxy.backend.response.header.query.QueryResponseHeader;
import
org.apache.shardingsphere.proxy.backend.response.header.update.UpdateResponseHeader;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
+import
org.apache.shardingsphere.proxy.backend.postgresql.response.header.query.PostgreSQLQueryHeaderBuilder;
import org.apache.shardingsphere.proxy.frontend.command.executor.ResponseType;
import
org.apache.shardingsphere.proxy.frontend.postgresql.command.PortalContext;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.dal.VariableAssignSegment;
@@ -63,6 +64,7 @@ import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import java.sql.SQLException;
+import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -115,7 +117,9 @@ class PostgreSQLComQueryExecutorTest {
@Test
void assertExecuteQueryWithColumnDescription() throws SQLException,
ReflectiveOperationException {
QueryResponseHeader queryResponseHeader =
mock(QueryResponseHeader.class);
-
when(queryResponseHeader.getQueryHeaders()).thenReturn(Collections.singletonList(new
QueryHeader("schema", "table", "label", "column", 1, "type", 2, 3, true, true,
true, true)));
+ when(queryResponseHeader.getQueryHeaders()).thenReturn(
+ Collections.singletonList(new QueryHeader("schema", "table",
"label", "column", Types.STRUCT, "record_type", 2, 3, true, true, true, true,
+
Collections.singletonMap(PostgreSQLQueryHeaderBuilder.TYPE_OID, 2249))));
when(proxyBackendHandler.execute()).thenReturn(queryResponseHeader);
Collection<DatabasePacket> actual = queryExecutor.execute();
PostgreSQLRowDescriptionPacket rowDescriptionPacket =
(PostgreSQLRowDescriptionPacket) actual.iterator().next();
@@ -126,7 +130,7 @@ class PostgreSQLComQueryExecutorTest {
assertThat(columnDescription.getColumnName(), is("label"));
assertThat(columnDescription.getColumnIndex(), is(1));
assertThat(columnDescription.getColumnLength(), is(2));
- assertThat(columnDescription.getTypeOID(), is(new
PostgreSQLColumnDescription("column", 1, 1, 2, "type").getTypeOID()));
+ assertThat(columnDescription.getTypeOID(), is(2249));
assertThat(queryExecutor.getResponseType(), is(ResponseType.QUERY));
}