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 e3ab362512e Fix JDBC database null handling and preserve original
exceptions (#40239)
e3ab362512e is described below
commit e3ab362512ee71f6235f69db7868a92f2ed8e544
Author: Liang Zhang <[email protected]>
AuthorDate: Mon Oct 19 20:27:02 2026 +0800
Fix JDBC database null handling and preserve original exceptions (#40239)
---
.../jdbc/adapter/AbstractStatementAdapter.java | 10 +-
.../core/connection/ShardingSphereConnection.java | 2 +-
.../metadata/ShardingSphereDatabaseMetaData.java | 8 +-
.../core/statement/DistSQLStatementExecutor.java | 3 +-
.../core/statement/ShardingSphereStatement.java | 36 ++--
.../connection/ShardingSphereConnectionTest.java | 184 ++++++++++++++++
.../ShardingSphereDatabaseMetaDataTest.java | 94 ++++++++-
.../statement/DistSQLStatementExecutorTest.java | 71 ++++++-
.../ShardingSpherePreparedStatementTest.java | 172 +++++++++++++++
.../statement/ShardingSphereStatementTest.java | 235 ++++++++++++++++++++-
10 files changed, 783 insertions(+), 32 deletions(-)
diff --git
a/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/adapter/AbstractStatementAdapter.java
b/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/adapter/AbstractStatementAdapter.java
index 1146a540f51..a6ab8aedf45 100644
---
a/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/adapter/AbstractStatementAdapter.java
+++
b/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/adapter/AbstractStatementAdapter.java
@@ -28,6 +28,7 @@ import
org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConne
import org.apache.shardingsphere.driver.jdbc.core.statement.StatementManager;
import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
+import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
import
org.apache.shardingsphere.infra.session.connection.transaction.TransactionConnectionContext;
import
org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
import
org.apache.shardingsphere.sql.parser.statement.core.statement.type.tcl.CommitStatement;
@@ -81,7 +82,7 @@ public abstract class AbstractStatementAdapter extends
WrapperAdapter implements
if (!transactionContext.isExceptionOccur()) {
return;
}
- DatabaseType databaseType =
connection.getContextManager().getMetaDataContexts().getMetaData().getDatabase(connection.getCurrentDatabaseName()).getProtocolType();
+ DatabaseType databaseType =
connection.getContextManager().getDatabase(connection.getCurrentDatabaseName()).getProtocolType();
if (new
DatabaseTypeRegistry(databaseType).getDialectDatabaseMetaData().getTransactionOption().isAllowCommitAndRollbackOnlyWhenTransactionFailed())
{
ShardingSpherePreconditions.checkState(sqlStatement instanceof
CommitStatement || sqlStatement instanceof RollbackStatement,
() -> new SQLFeatureNotSupportedException("Current
transaction is aborted, commands ignored until end of transaction block."));
@@ -96,8 +97,11 @@ public abstract class AbstractStatementAdapter extends
WrapperAdapter implements
protected final void handleExceptionInTransaction(final
ShardingSphereConnection connection, final ShardingSphereMetaData metaData) {
if
(connection.getDatabaseConnectionManager().getConnectionContext().getTransactionContext().isInTransaction())
{
- DatabaseType databaseType =
metaData.getDatabase(connection.getCurrentDatabaseName()).getProtocolType();
- DialectDatabaseMetaData dialectDatabaseMetaData = new
DatabaseTypeRegistry(databaseType).getDialectDatabaseMetaData();
+ ShardingSphereDatabase database =
metaData.getDatabase(connection.getCurrentDatabaseName());
+ if (null == database) {
+ return;
+ }
+ DialectDatabaseMetaData dialectDatabaseMetaData = new
DatabaseTypeRegistry(database.getProtocolType()).getDialectDatabaseMetaData();
if
(dialectDatabaseMetaData.getSchemaOption().getDefaultSchema().isPresent()) {
connection.getDatabaseConnectionManager().getConnectionContext().getTransactionContext().setExceptionOccur(true);
}
diff --git
a/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/connection/ShardingSphereConnection.java
b/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/connection/ShardingSphereConnection.java
index 5b7c2fe5f80..76bafd178b0 100644
---
a/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/connection/ShardingSphereConnection.java
+++
b/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/connection/ShardingSphereConnection.java
@@ -238,7 +238,7 @@ public final class ShardingSphereConnection extends
AbstractConnectionAdapter {
}
private boolean isSchemaSupportedDatabaseType() {
- DatabaseType databaseType =
contextManager.getMetaDataContexts().getMetaData().getDatabase(currentDatabaseName).getProtocolType();
+ DatabaseType databaseType =
contextManager.getDatabase(currentDatabaseName).getProtocolType();
return new
DatabaseTypeRegistry(databaseType).getDialectDatabaseMetaData().getSchemaOption().getDefaultSchema().isPresent();
}
diff --git
a/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/datasource/metadata/ShardingSphereDatabaseMetaData.java
b/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/datasource/metadata/ShardingSphereDatabaseMetaData.java
index 98fca70d984..62220572bf3 100644
---
a/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/datasource/metadata/ShardingSphereDatabaseMetaData.java
+++
b/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/datasource/metadata/ShardingSphereDatabaseMetaData.java
@@ -50,7 +50,7 @@ public final class ShardingSphereDatabaseMetaData extends
AdaptedDatabaseMetaDat
public ShardingSphereDatabaseMetaData(final ShardingSphereConnection
connection) throws SQLException {
super(connection.getDatabaseConnectionManager().getRandomConnection().getMetaData());
this.connection = connection;
- rules =
connection.getContextManager().getMetaDataContexts().getMetaData().getDatabase(connection.getCurrentDatabaseName()).getRuleMetaData().getRules();
+ rules =
connection.getContextManager().getDatabase(connection.getCurrentDatabaseName()).getRuleMetaData().getRules();
}
@Override
@@ -231,8 +231,7 @@ public final class ShardingSphereDatabaseMetaData extends
AdaptedDatabaseMetaDat
return null;
}
// TODO consider get actual catalog by logic catalog rather than
random physical datasource's catalog.
- ConnectionProperties connectionProps = connection.getContextManager()
-
.getMetaDataContexts().getMetaData().getDatabase(connection.getCurrentDatabaseName()).getResourceMetaData().getStorageUnits().get(getDataSourceName()).getConnectionProperties();
+ ConnectionProperties connectionProps =
connection.getContextManager().getStorageUnits(connection.getCurrentDatabaseName()).get(getDataSourceName()).getConnectionProperties();
return connectionProps.getCatalog();
}
@@ -241,8 +240,7 @@ public final class ShardingSphereDatabaseMetaData extends
AdaptedDatabaseMetaDat
return null;
}
// TODO consider get actual schema by logic catalog rather than random
physical datasource's schema.
- ConnectionProperties connectionProps = connection.getContextManager()
-
.getMetaDataContexts().getMetaData().getDatabase(connection.getCurrentDatabaseName()).getResourceMetaData().getStorageUnits().get(getDataSourceName()).getConnectionProperties();
+ ConnectionProperties connectionProps =
connection.getContextManager().getStorageUnits(connection.getCurrentDatabaseName()).get(getDataSourceName()).getConnectionProperties();
return
Optional.ofNullable(connectionProps.getSchema()).map(String::toUpperCase).orElse(null);
}
diff --git
a/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/statement/DistSQLStatementExecutor.java
b/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/statement/DistSQLStatementExecutor.java
index 4548a20c34b..29fb349f970 100644
---
a/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/statement/DistSQLStatementExecutor.java
+++
b/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/statement/DistSQLStatementExecutor.java
@@ -87,8 +87,7 @@ final class DistSQLStatementExecutor {
private DistSQLConnectionContext createConnectionContext(final
QueryContext queryContext) {
DistSQLConnectionContext result = new
DistSQLConnectionContext(queryContext,
connection.getDatabaseConnectionManager().getConnectionSize(),
-
connection.getContextManager().getMetaDataContexts().getMetaData().getDatabase(connection.getCurrentDatabaseName()).getProtocolType(),
- connection.getDatabaseConnectionManager(), statementManager);
+
connection.getContextManager().getDatabase(connection.getCurrentDatabaseName()).getProtocolType(),
connection.getDatabaseConnectionManager(), statementManager);
result.setProcessId(connection.getProcessId());
return result;
}
diff --git
a/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/statement/ShardingSphereStatement.java
b/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/statement/ShardingSphereStatement.java
index 06e9221a960..c90c8cb7c0a 100644
---
a/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/statement/ShardingSphereStatement.java
+++
b/jdbc/src/main/java/org/apache/shardingsphere/driver/jdbc/core/statement/ShardingSphereStatement.java
@@ -23,6 +23,8 @@ import
org.apache.shardingsphere.database.connector.core.metadata.database.metad
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import
org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
import
org.apache.shardingsphere.database.exception.core.SQLExceptionTransformEngine;
+import
org.apache.shardingsphere.database.exception.core.exception.SQLDialectException;
+import
org.apache.shardingsphere.database.exception.core.exception.protocol.DatabaseProtocolException;
import org.apache.shardingsphere.distsql.statement.DistSQLStatement;
import
org.apache.shardingsphere.driver.executor.callback.add.StatementAddCallback;
import
org.apache.shardingsphere.driver.executor.callback.execute.StatementExecuteCallback;
@@ -40,6 +42,7 @@ import
org.apache.shardingsphere.infra.binder.context.statement.type.CommonSQLSt
import
org.apache.shardingsphere.infra.binder.context.statement.type.dml.InsertStatementContext;
import org.apache.shardingsphere.infra.binder.engine.SQLBindEngine;
import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
+import org.apache.shardingsphere.infra.exception.generic.UnknownSQLException;
import
org.apache.shardingsphere.infra.exception.kernel.syntax.EmptySQLException;
import
org.apache.shardingsphere.infra.executor.sql.prepare.driver.jdbc.JDBCDriverType;
import
org.apache.shardingsphere.infra.executor.sql.prepare.driver.jdbc.StatementOption;
@@ -106,9 +109,10 @@ public final class ShardingSphereStatement extends
AbstractStatementAdapter {
this.connection = connection;
metaData =
connection.getContextManager().getMetaDataContexts().getMetaData();
statementOption = new StatementOption(resultSetType,
resultSetConcurrency, resultSetHoldability);
+ ShardingSphereDatabase currentDatabase =
connection.getContextManager().getDatabase(connection.getCurrentDatabaseName());
statementManager = new StatementManager();
connection.registerStatementManager(statementManager);
- driverExecutorFacade = new DriverExecutorFacade(connection,
statementOption, statementManager, JDBCDriverType.STATEMENT,
metaData.getDatabase(connection.getCurrentDatabaseName()));
+ driverExecutorFacade = new DriverExecutorFacade(connection,
statementOption, statementManager, JDBCDriverType.STATEMENT, currentDatabase);
distSQLStatementExecutor = new DistSQLStatementExecutor(connection,
statementManager, this);
batchStatementExecutor = new BatchStatementExecutor(this);
statements = new LinkedList<>();
@@ -133,10 +137,18 @@ public final class ShardingSphereStatement extends
AbstractStatementAdapter {
// CHECKSTYLE:ON
handleExceptionInTransaction(connection, metaData);
currentResultSet = null;
- throw SQLExceptionTransformEngine.toSQLException(ex,
metaData.getDatabase(usedDatabaseName).getProtocolType());
+ throw toSQLException(ex);
}
}
+ private SQLException toSQLException(final Exception cause) {
+ if (cause instanceof SQLDialectException && !(cause instanceof
DatabaseProtocolException)) {
+ ShardingSphereDatabase usedDatabase =
metaData.getDatabase(usedDatabaseName);
+ return null == usedDatabase ? new
UnknownSQLException(cause).toSQLException() :
SQLExceptionTransformEngine.toSQLException(cause,
usedDatabase.getProtocolType());
+ }
+ return SQLExceptionTransformEngine.toSQLException(cause,
metaData.getProtocolType());
+ }
+
@Override
public int executeUpdate(final String sql) throws SQLException {
try {
@@ -145,7 +157,7 @@ public final class ShardingSphereStatement extends
AbstractStatementAdapter {
} catch (final RuntimeException | SQLException ex) {
// CHECKSTYLE:ON
handleExceptionInTransaction(connection, metaData);
- throw SQLExceptionTransformEngine.toSQLException(ex,
metaData.getDatabase(usedDatabaseName).getProtocolType());
+ throw toSQLException(ex);
}
}
@@ -160,7 +172,7 @@ public final class ShardingSphereStatement extends
AbstractStatementAdapter {
} catch (final RuntimeException | SQLException ex) {
// CHECKSTYLE:ON
handleExceptionInTransaction(connection, metaData);
- throw SQLExceptionTransformEngine.toSQLException(ex,
metaData.getDatabase(usedDatabaseName).getProtocolType());
+ throw toSQLException(ex);
}
}
@@ -173,7 +185,7 @@ public final class ShardingSphereStatement extends
AbstractStatementAdapter {
} catch (final RuntimeException | SQLException ex) {
// CHECKSTYLE:ON
handleExceptionInTransaction(connection, metaData);
- throw SQLExceptionTransformEngine.toSQLException(ex,
metaData.getDatabase(usedDatabaseName).getProtocolType());
+ throw toSQLException(ex);
}
}
@@ -186,7 +198,7 @@ public final class ShardingSphereStatement extends
AbstractStatementAdapter {
} catch (final RuntimeException | SQLException ex) {
// CHECKSTYLE:ON
handleExceptionInTransaction(connection, metaData);
- throw SQLExceptionTransformEngine.toSQLException(ex,
metaData.getDatabase(usedDatabaseName).getProtocolType());
+ throw toSQLException(ex);
}
}
@@ -211,7 +223,7 @@ public final class ShardingSphereStatement extends
AbstractStatementAdapter {
} catch (final SQLException ex) {
// CHECKSTYLE:ON
handleExceptionInTransaction(connection, metaData);
- throw SQLExceptionTransformEngine.toSQLException(ex,
metaData.getDatabase(usedDatabaseName).getProtocolType());
+ throw ex;
}
}
@@ -226,7 +238,7 @@ public final class ShardingSphereStatement extends
AbstractStatementAdapter {
} catch (final SQLException ex) {
// CHECKSTYLE:ON
handleExceptionInTransaction(connection, metaData);
- throw SQLExceptionTransformEngine.toSQLException(ex,
metaData.getDatabase(usedDatabaseName).getProtocolType());
+ throw ex;
}
}
@@ -239,7 +251,7 @@ public final class ShardingSphereStatement extends
AbstractStatementAdapter {
} catch (final SQLException ex) {
// CHECKSTYLE:ON
handleExceptionInTransaction(connection, metaData);
- throw SQLExceptionTransformEngine.toSQLException(ex,
metaData.getDatabase(usedDatabaseName).getProtocolType());
+ throw ex;
}
}
@@ -252,7 +264,7 @@ public final class ShardingSphereStatement extends
AbstractStatementAdapter {
} catch (final SQLException ex) {
// CHECKSTYLE:ON
handleExceptionInTransaction(connection, metaData);
- throw SQLExceptionTransformEngine.toSQLException(ex,
metaData.getDatabase(usedDatabaseName).getProtocolType());
+ throw ex;
}
}
@@ -279,7 +291,7 @@ public final class ShardingSphereStatement extends
AbstractStatementAdapter {
// CHECKSTYLE:OFF
} catch (final RuntimeException ex) {
// CHECKSTYLE:ON
- throw SQLExceptionTransformEngine.toSQLException(ex,
metaData.getDatabase(usedDatabaseName).getProtocolType());
+ throw toSQLException(ex);
}
}
@@ -416,7 +428,7 @@ public final class ShardingSphereStatement extends
AbstractStatementAdapter {
private String getGeneratedKeysColumnName(final String columnName) {
Optional<DialectGeneratedKeyOption> generatedKeyOption =
- new
DatabaseTypeRegistry(metaData.getDatabase(usedDatabaseName).getProtocolType()).getDialectDatabaseMetaData().getGeneratedKeyOption();
+ new
DatabaseTypeRegistry(connection.getContextManager().getDatabase(usedDatabaseName).getProtocolType()).getDialectDatabaseMetaData().getGeneratedKeyOption();
return generatedKeyOption.isPresent() ?
generatedKeyOption.get().getColumnName() : columnName;
}
diff --git
a/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/connection/ShardingSphereConnectionTest.java
b/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/connection/ShardingSphereConnectionTest.java
index 47847e8cbaf..0db6fb19a86 100644
---
a/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/connection/ShardingSphereConnectionTest.java
+++
b/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/connection/ShardingSphereConnectionTest.java
@@ -18,10 +18,15 @@
package org.apache.shardingsphere.driver.jdbc.core.connection;
import lombok.SneakyThrows;
+import
org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import
org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
+import
org.apache.shardingsphere.database.exception.core.exception.syntax.database.UnknownDatabaseException;
import
org.apache.shardingsphere.infra.executor.sql.execute.engine.ConnectionMode;
import
org.apache.shardingsphere.infra.metadata.database.resource.unit.StorageUnit;
import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
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.test.infra.fixture.jdbc.MockedDataSource;
import org.apache.shardingsphere.transaction.ConnectionTransaction;
@@ -30,21 +35,29 @@ import
org.apache.shardingsphere.transaction.api.TransactionType;
import
org.apache.shardingsphere.transaction.config.TransactionRuleConfiguration;
import org.apache.shardingsphere.transaction.rule.TransactionRule;
import org.junit.jupiter.api.Test;
+import org.mockito.MockedConstruction;
import org.mockito.internal.configuration.plugins.Plugins;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.Savepoint;
import java.util.Collections;
import java.util.Optional;
import java.util.Properties;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -140,6 +153,177 @@ class ShardingSphereConnectionTest {
return result;
}
+ @Test
+ void assertRollbackSavepointWithDroppedDatabase() throws SQLException {
+ ContextManager contextManager = mockContextManager();
+ try (ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db", contextManager)) {
+ UnknownDatabaseException expected = new
UnknownDatabaseException("foo_db");
+ mockDroppedDatabase(contextManager, expected);
+ assertThat(assertThrows(UnknownDatabaseException.class, () ->
connection.rollback(mock(Savepoint.class))), sameInstance(expected));
+ }
+ }
+
+ @Test
+ void assertSetNamedSavepointWithDroppedDatabase() throws SQLException {
+ ContextManager contextManager = mockContextManager();
+ try (ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db", contextManager)) {
+ UnknownDatabaseException expected = new
UnknownDatabaseException("foo_db");
+ mockDroppedDatabase(contextManager, expected);
+ assertThat(assertThrows(UnknownDatabaseException.class, () ->
connection.setSavepoint("foo_savepoint")), sameInstance(expected));
+ }
+ }
+
+ @Test
+ void assertSetSavepointWithDroppedDatabase() throws SQLException {
+ ContextManager contextManager = mockContextManager();
+ try (ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db", contextManager)) {
+ UnknownDatabaseException expected = new
UnknownDatabaseException("foo_db");
+ mockDroppedDatabase(contextManager, expected);
+ assertThat(assertThrows(UnknownDatabaseException.class,
connection::setSavepoint), sameInstance(expected));
+ }
+ }
+
+ @Test
+ void assertReleaseSavepointWithDroppedDatabase() throws SQLException {
+ ContextManager contextManager = mockContextManager();
+ try (ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db", contextManager)) {
+ UnknownDatabaseException expected = new
UnknownDatabaseException("foo_db");
+ mockDroppedDatabase(contextManager, expected);
+ assertThat(assertThrows(UnknownDatabaseException.class, () ->
connection.releaseSavepoint(mock(Savepoint.class))), sameInstance(expected));
+ }
+ }
+
+ private void mockDroppedDatabase(final ContextManager contextManager,
final UnknownDatabaseException ex) {
+
when(contextManager.getMetaDataContexts().getMetaData().getDatabase("foo_db")).thenReturn(null);
+ when(contextManager.getDatabase("foo_db")).thenThrow(ex);
+ }
+
+ @Test
+ void assertSetSavepointOutsidePostgreSQLTransaction() throws SQLException {
+ ContextManager contextManager =
mockContextManagerWithDatabaseType("PostgreSQL");
+ DialectDatabaseMetaData dialectDatabaseMetaData =
mock(DialectDatabaseMetaData.class, RETURNS_DEEP_STUBS);
+
when(dialectDatabaseMetaData.getSchemaOption().getDefaultSchema()).thenReturn(Optional.of("public"));
+ try (
+ ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db", contextManager);
+ MockedConstruction<DatabaseTypeRegistry> ignored =
mockConstruction(DatabaseTypeRegistry.class,
+ (registry, context) ->
when(registry.getDialectDatabaseMetaData()).thenReturn(dialectDatabaseMetaData)))
{
+ SQLFeatureNotSupportedException actual =
assertThrows(SQLFeatureNotSupportedException.class, connection::setSavepoint);
+ assertThat(actual.getMessage(), is("Savepoint can only be used in
transaction blocks"));
+ }
+ }
+
+ @Test
+ void assertSetNamedSavepointOutsideMySQLTransaction() throws SQLException {
+ try (ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db",
mockContextManagerWithDatabaseType("MySQL"))) {
+ DriverDatabaseConnectionManager databaseConnectionManager =
mockConnectionManager(connection, mock(ConnectionTransaction.class));
+ Savepoint expected = mock(Savepoint.class);
+
when(databaseConnectionManager.setSavepoint("foo_savepoint")).thenReturn(expected);
+ assertThat(connection.setSavepoint("foo_savepoint"),
sameInstance(expected));
+ }
+ }
+
+ @Test
+ void assertSetSavepointOutsideMySQLTransaction() throws SQLException {
+ try (ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db",
mockContextManagerWithDatabaseType("MySQL"))) {
+ DriverDatabaseConnectionManager databaseConnectionManager =
mockConnectionManager(connection, mock(ConnectionTransaction.class));
+ Savepoint expected = mock(Savepoint.class);
+
when(databaseConnectionManager.setSavepoint()).thenReturn(expected);
+ assertThat(connection.setSavepoint(), sameInstance(expected));
+ }
+ }
+
+ @Test
+ void assertRollbackSavepointWithSQLException() throws SQLException {
+ try (ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db",
mockContextManagerWithDatabaseType("MySQL"))) {
+ DriverDatabaseConnectionManager databaseConnectionManager =
mockConnectionManager(connection, mock(ConnectionTransaction.class));
+ Savepoint savepoint = mock(Savepoint.class);
+ SQLException expected = new SQLException("foo_savepoint failure");
+
doThrow(expected).when(databaseConnectionManager).rollback(savepoint);
+ assertThat(assertThrows(SQLException.class, () ->
connection.rollback(savepoint)), sameInstance(expected));
+ }
+ }
+
+ @Test
+ void assertReleaseSavepointOutsideMySQLTransaction() throws SQLException {
+ try (ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db",
mockContextManagerWithDatabaseType("MySQL"))) {
+ DriverDatabaseConnectionManager databaseConnectionManager =
mockConnectionManager(connection, mock(ConnectionTransaction.class));
+ Savepoint savepoint = mock(Savepoint.class);
+ connection.releaseSavepoint(savepoint);
+ verify(databaseConnectionManager,
never()).releaseSavepoint(savepoint);
+ }
+ }
+
+ private ContextManager mockContextManagerWithDatabaseType(final String
databaseType) {
+ ContextManager result = mockContextManager();
+
when(result.getDatabase("foo_db").getProtocolType()).thenReturn(TypedSPILoader.getService(DatabaseType.class,
databaseType));
+ return result;
+ }
+
+ @Test
+ void assertRollbackSavepointInTransaction() throws SQLException {
+ ContextManager contextManager = mockContextManager();
+ try (ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db", contextManager)) {
+ DriverDatabaseConnectionManager databaseConnectionManager =
mockConnectionManagerInTransaction(connection);
+ Savepoint savepoint = mock(Savepoint.class);
+ connection.rollback(savepoint);
+ verify(databaseConnectionManager).rollback(savepoint);
+ verify(contextManager, never()).getDatabase("foo_db");
+ }
+ }
+
+ @Test
+ void assertSetNamedSavepointInTransaction() throws SQLException {
+ ContextManager contextManager = mockContextManager();
+ try (ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db", contextManager)) {
+ DriverDatabaseConnectionManager databaseConnectionManager =
mockConnectionManagerInTransaction(connection);
+ Savepoint expected = mock(Savepoint.class);
+
when(databaseConnectionManager.setSavepoint("foo_savepoint")).thenReturn(expected);
+ assertThat(connection.setSavepoint("foo_savepoint"),
sameInstance(expected));
+ verify(contextManager, never()).getDatabase("foo_db");
+ }
+ }
+
+ @Test
+ void assertSetSavepointInTransaction() throws SQLException {
+ ContextManager contextManager = mockContextManager();
+ try (ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db", contextManager)) {
+ DriverDatabaseConnectionManager databaseConnectionManager =
mockConnectionManagerInTransaction(connection);
+ Savepoint expected = mock(Savepoint.class);
+
when(databaseConnectionManager.setSavepoint()).thenReturn(expected);
+ assertThat(connection.setSavepoint(), sameInstance(expected));
+ verify(contextManager, never()).getDatabase("foo_db");
+ }
+ }
+
+ @Test
+ void assertReleaseSavepointInTransaction() throws SQLException {
+ ContextManager contextManager = mockContextManager();
+ try (ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db", contextManager)) {
+ DriverDatabaseConnectionManager databaseConnectionManager =
mockConnectionManagerInTransaction(connection);
+ Savepoint savepoint = mock(Savepoint.class);
+ connection.releaseSavepoint(savepoint);
+ verify(databaseConnectionManager).releaseSavepoint(savepoint);
+ verify(contextManager, never()).getDatabase("foo_db");
+ }
+ }
+
+ private DriverDatabaseConnectionManager
mockConnectionManagerInTransaction(final ShardingSphereConnection connection) {
+ ConnectionTransaction connectionTransaction =
mock(ConnectionTransaction.class);
+ when(connectionTransaction.isHoldTransaction(true)).thenReturn(true);
+ return mockConnectionManager(connection, connectionTransaction);
+ }
+
+ @Test
+ void assertSetSavepointOnClosedConnection() throws SQLException {
+ ContextManager contextManager = mockContextManager();
+ try (ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db", contextManager)) {
+ connection.close();
+ SQLException actual = assertThrows(SQLException.class,
connection::setSavepoint);
+ assertThat(actual.getMessage(), is("Connection has been closed."));
+ verify(contextManager, never()).getDatabase("foo_db");
+ }
+ }
+
@Test
void assertIsValidWhenEmptyConnection() throws SQLException {
try (ShardingSphereConnection connection = new
ShardingSphereConnection("foo_db", mockContextManager())) {
diff --git
a/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/datasource/metadata/ShardingSphereDatabaseMetaDataTest.java
b/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/datasource/metadata/ShardingSphereDatabaseMetaDataTest.java
index 24ed42a7b67..3cdd5fc2cf0 100644
---
a/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/datasource/metadata/ShardingSphereDatabaseMetaDataTest.java
+++
b/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/datasource/metadata/ShardingSphereDatabaseMetaDataTest.java
@@ -18,10 +18,12 @@
package org.apache.shardingsphere.driver.jdbc.core.datasource.metadata;
import
org.apache.shardingsphere.database.connector.core.jdbcurl.parser.ConnectionProperties;
+import
org.apache.shardingsphere.database.exception.core.exception.syntax.database.UnknownDatabaseException;
import
org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection;
import
org.apache.shardingsphere.driver.jdbc.core.resultset.DatabaseMetaDataResultSet;
import org.apache.shardingsphere.infra.instance.ComputeNodeInstanceContext;
import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
+import
org.apache.shardingsphere.infra.metadata.database.resource.unit.StorageUnit;
import org.apache.shardingsphere.mode.metadata.MetaDataContexts;
import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
import
org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
@@ -30,6 +32,9 @@ import
org.apache.shardingsphere.test.infra.fixture.jdbc.MockedDataSource;
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;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@@ -44,10 +49,14 @@ import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Collections;
+import java.util.Map;
+import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isA;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -69,6 +78,9 @@ class ShardingSphereDatabaseMetaDataTest {
@Mock
private DatabaseMetaData databaseMetaData;
+ @Mock
+ private ConnectionProperties connectionProps;
+
@Mock
private ResultSet resultSet;
@@ -91,9 +103,11 @@ class ShardingSphereDatabaseMetaDataTest {
when(shardingSphereConnection.getCurrentDatabaseName()).thenReturn("foo_db");
ShardingSphereDatabase database = mock(ShardingSphereDatabase.class,
RETURNS_DEEP_STUBS);
when(metaDataContexts.getMetaData().getDatabase(shardingSphereConnection.getCurrentDatabaseName())).thenReturn(database);
+
when(shardingSphereConnection.getContextManager().getDatabase("foo_db")).thenReturn(database);
+ Map<String, StorageUnit> storageUnits =
database.getResourceMetaData().getStorageUnits();
+
when(shardingSphereConnection.getContextManager().getStorageUnits("foo_db")).thenReturn(storageUnits);
ShardingRule shardingRule = mockShardingRule();
when(database.getRuleMetaData().getRules()).thenReturn(Collections.singleton(shardingRule));
- ConnectionProperties connectionProps =
mock(ConnectionProperties.class);
when(connectionProps.getCatalog()).thenReturn("test");
when(connectionProps.getSchema()).thenReturn("test");
when(database.getResourceMetaData().getStorageUnits().get(DATA_SOURCE_NAME).getConnectionProperties()).thenReturn(connectionProps);
@@ -107,6 +121,23 @@ class ShardingSphereDatabaseMetaDataTest {
return new ShardingRule(ruleConfig,
Collections.singletonMap(DATA_SOURCE_NAME, new MockedDataSource()),
mock(ComputeNodeInstanceContext.class), Collections.emptyList());
}
+ @Test
+ void assertConstructAfterDatabaseRemoval() {
+ UnknownDatabaseException expectedException = new
UnknownDatabaseException("foo_db");
+
when(metaDataContexts.getMetaData().getDatabase("foo_db")).thenReturn(null);
+
when(shardingSphereConnection.getContextManager().getDatabase("foo_db")).thenThrow(expectedException);
+ assertThat(assertThrows(UnknownDatabaseException.class, () -> new
ShardingSphereDatabaseMetaData(shardingSphereConnection)),
sameInstance(expectedException));
+ }
+
+ @Test
+ void assertConstructWithPhysicalMetadataExceptionAfterDatabaseRemoval()
throws SQLException {
+ SQLException expectedException = new SQLException("physical metadata
failed");
+ when(connection.getMetaData()).thenThrow(expectedException);
+
when(metaDataContexts.getMetaData().getDatabase("foo_db")).thenReturn(null);
+
when(shardingSphereConnection.getContextManager().getDatabase("foo_db")).thenThrow(new
UnknownDatabaseException("foo_db"));
+ assertThat(assertThrows(SQLException.class, () -> new
ShardingSphereDatabaseMetaData(shardingSphereConnection)),
sameInstance(expectedException));
+ }
+
@Test
void assertGetURL() throws SQLException {
assertThat(shardingSphereDatabaseMetaData.getURL(),
is(databaseMetaData.getURL()));
@@ -889,10 +920,67 @@ class ShardingSphereDatabaseMetaDataTest {
assertThat(shardingSphereDatabaseMetaData.getSchemas(),
isA(DatabaseMetaDataResultSet.class));
}
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("getSchemasArguments")
+ void assertGetSchemasForCatalogAndSchemaPattern(final String name, final
String catalog, final String schemaPattern, final String expectedCatalog,
+ final String
expectedSchema) throws SQLException {
+ when(databaseMetaData.getSchemas(expectedCatalog,
expectedSchema)).thenReturn(resultSet);
+ assertThat(shardingSphereDatabaseMetaData.getSchemas(catalog,
schemaPattern), isA(DatabaseMetaDataResultSet.class));
+ }
+
+ private static Stream<Arguments> getSchemasArguments() {
+ return Stream.of(
+ Arguments.of("null catalog and schema", null, null, null,
null),
+ Arguments.of("catalog only", "foo_catalog", null, "test",
null),
+ Arguments.of("schema only", null, "foo_schema", null, "TEST"),
+ Arguments.of("catalog and schema", "foo_catalog",
"foo_schema", "test", "TEST"));
+ }
+
@Test
- void assertGetSchemasForCatalogAndSchemaPattern() throws SQLException {
+ void assertGetSchemasForCatalogAfterDatabaseRemoval() {
+ UnknownDatabaseException expectedException = new
UnknownDatabaseException("foo_db");
+
when(metaDataContexts.getMetaData().getDatabase("foo_db")).thenReturn(null);
+
when(shardingSphereConnection.getContextManager().getStorageUnits("foo_db")).thenThrow(expectedException);
+ assertThat(assertThrows(UnknownDatabaseException.class, () ->
shardingSphereDatabaseMetaData.getSchemas("foo_catalog", null)),
sameInstance(expectedException));
+ }
+
+ @Test
+ void assertGetSchemasForSchemaAfterDatabaseRemoval() {
+ UnknownDatabaseException expectedException = new
UnknownDatabaseException("foo_db");
+
when(metaDataContexts.getMetaData().getDatabase("foo_db")).thenReturn(null);
+
when(shardingSphereConnection.getContextManager().getStorageUnits("foo_db")).thenThrow(expectedException);
+ assertThat(assertThrows(UnknownDatabaseException.class, () ->
shardingSphereDatabaseMetaData.getSchemas(null, "foo_schema")),
sameInstance(expectedException));
+ }
+
+ @Test
+ void assertGetSchemasForNullCatalogAndSchemaAfterDatabaseRemoval() throws
SQLException {
+
when(metaDataContexts.getMetaData().getDatabase("foo_db")).thenReturn(null);
+
when(shardingSphereConnection.getContextManager().getStorageUnits("foo_db")).thenThrow(new
UnknownDatabaseException("foo_db"));
+ when(databaseMetaData.getSchemas(null, null)).thenReturn(resultSet);
+ assertThat(shardingSphereDatabaseMetaData.getSchemas(null, null),
isA(DatabaseMetaDataResultSet.class));
+ }
+
+ @Test
+ void assertGetSchemasWithNullPhysicalCatalog() throws SQLException {
+ when(connectionProps.getCatalog()).thenReturn(null);
+ when(databaseMetaData.getSchemas(null, "TEST")).thenReturn(resultSet);
+ assertThat(shardingSphereDatabaseMetaData.getSchemas("foo_catalog",
"foo_schema"), isA(DatabaseMetaDataResultSet.class));
+ }
+
+ @Test
+ void assertGetSchemasWithNullPhysicalSchema() throws SQLException {
+ when(connectionProps.getSchema()).thenReturn(null);
when(databaseMetaData.getSchemas("test", null)).thenReturn(resultSet);
- assertThat(shardingSphereDatabaseMetaData.getSchemas("test", null),
isA(DatabaseMetaDataResultSet.class));
+ assertThat(shardingSphereDatabaseMetaData.getSchemas("foo_catalog",
"foo_schema"), isA(DatabaseMetaDataResultSet.class));
+ }
+
+ @Test
+ void assertGetSchemasWithPhysicalMetadataExceptionAfterDatabaseRemoval()
throws SQLException {
+ SQLException expectedException = new SQLException("physical metadata
failed");
+ when(connection.getMetaData()).thenThrow(expectedException);
+
when(metaDataContexts.getMetaData().getDatabase("foo_db")).thenReturn(null);
+
when(shardingSphereConnection.getContextManager().getStorageUnits("foo_db")).thenThrow(new
UnknownDatabaseException("foo_db"));
+ assertThat(assertThrows(SQLException.class, () ->
shardingSphereDatabaseMetaData.getSchemas("foo_catalog", "foo_schema")),
sameInstance(expectedException));
}
@Test
diff --git
a/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/statement/DistSQLStatementExecutorTest.java
b/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/statement/DistSQLStatementExecutorTest.java
index 3c0bc503798..f364e71561b 100644
---
a/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/statement/DistSQLStatementExecutorTest.java
+++
b/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/statement/DistSQLStatementExecutorTest.java
@@ -17,18 +17,23 @@
package org.apache.shardingsphere.driver.jdbc.core.statement;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import
org.apache.shardingsphere.database.exception.core.exception.syntax.database.UnknownDatabaseException;
import
org.apache.shardingsphere.distsql.handler.context.DistSQLConnectionContext;
import
org.apache.shardingsphere.distsql.handler.engine.query.DistSQLQueryExecuteEngine;
import
org.apache.shardingsphere.distsql.handler.engine.update.DistSQLUpdateExecuteEngine;
import org.apache.shardingsphere.distsql.segment.AlgorithmSegment;
import org.apache.shardingsphere.distsql.statement.DistSQLStatement;
+import
org.apache.shardingsphere.distsql.statement.type.ral.queryable.QueryableRALStatement;
import
org.apache.shardingsphere.distsql.statement.type.ral.updatable.LockClusterStatement;
import
org.apache.shardingsphere.distsql.statement.type.rdl.resource.unit.type.UnregisterStorageUnitStatement;
+import org.apache.shardingsphere.distsql.statement.type.rql.RQLStatement;
import org.apache.shardingsphere.distsql.statement.type.rul.sql.ParseStatement;
import
org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection;
import
org.apache.shardingsphere.infra.exception.generic.UnsupportedSQLOperationException;
import
org.apache.shardingsphere.infra.merge.result.impl.local.LocalDataQueryResultRow;
import org.apache.shardingsphere.infra.session.query.QueryContext;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.mode.exclusive.ExclusiveOperatorEngine;
import
org.apache.shardingsphere.mode.exclusive.callback.ExclusiveOperationVoidCallback;
import org.apache.shardingsphere.mode.manager.ContextManager;
@@ -50,6 +55,7 @@ import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -68,6 +74,10 @@ class DistSQLStatementExecutorTest {
void assertExecuteQuery() throws SQLException {
QueryContext queryContext = createQueryContext(new
ParseStatement("SELECT 1"));
ShardingSphereConnection connection =
mock(ShardingSphereConnection.class, RETURNS_DEEP_STUBS);
+ DatabaseType protocolType =
TypedSPILoader.getService(DatabaseType.class, "PostgreSQL");
+ when(connection.getCurrentDatabaseName()).thenReturn("foo_db");
+
when(connection.getContextManager().getDatabase("foo_db").getProtocolType()).thenReturn(protocolType);
+
when(connection.getContextManager().getMetaDataContexts().getMetaData().getProtocolType()).thenReturn(TypedSPILoader.getService(DatabaseType.class,
"MySQL"));
when(connection.getProcessId()).thenReturn("foo_process_id");
Statement statement = mock(Statement.class);
when(statement.getResultSetType()).thenReturn(ResultSet.TYPE_FORWARD_ONLY);
@@ -82,6 +92,8 @@ class DistSQLStatementExecutorTest {
verify(mockedEngines.constructed().get(0)).executeQuery();
assertThat(actualConnectionContext.get().getQueryContext(),
sameInstance(queryContext));
assertThat(actualConnectionContext.get().getProcessId(),
is("foo_process_id"));
+ assertThat(actualConnectionContext.get().getProtocolType(),
sameInstance(protocolType));
+
assertNull(queryContext.getSqlStatementContext().getSqlStatement().getDatabaseType());
assertTrue(actual.next());
assertThat(actual.getString(1), is("foo_value"));
}
@@ -92,12 +104,53 @@ class DistSQLStatementExecutorTest {
void assertExecuteUpdate() throws SQLException {
QueryContext queryContext = createQueryContext(new
UnregisterStorageUnitStatement(Collections.singleton("foo_ds"), false, false));
ShardingSphereConnection connection =
mock(ShardingSphereConnection.class, RETURNS_DEEP_STUBS);
- try (MockedConstruction<DistSQLUpdateExecuteEngine> mockedEngines =
mockConstruction(DistSQLUpdateExecuteEngine.class)) {
+ DatabaseType protocolType =
TypedSPILoader.getService(DatabaseType.class, "PostgreSQL");
+ when(connection.getCurrentDatabaseName()).thenReturn("foo_db");
+
when(connection.getContextManager().getDatabase("foo_db").getProtocolType()).thenReturn(protocolType);
+ AtomicReference<DistSQLConnectionContext> actualConnectionContext =
new AtomicReference<>();
+ try (
+ MockedConstruction<DistSQLUpdateExecuteEngine> mockedEngines =
mockConstruction(DistSQLUpdateExecuteEngine.class,
+ (mock, context) ->
actualConnectionContext.set((DistSQLConnectionContext)
context.arguments().get(3)))) {
new DistSQLStatementExecutor(connection,
mock(StatementManager.class),
mock(Statement.class)).executeUpdate(queryContext);
verify(mockedEngines.constructed().get(0)).executeUpdate();
+ assertThat(actualConnectionContext.get().getProtocolType(),
sameInstance(protocolType));
+
assertNull(queryContext.getSqlStatementContext().getSqlStatement().getDatabaseType());
}
}
+ @Test
+ void assertExecuteQueryWithoutDatabase() {
+ ShardingSphereConnection connection =
mock(ShardingSphereConnection.class, RETURNS_DEEP_STUBS);
+ when(connection.getCurrentDatabaseName()).thenReturn("foo_db");
+
when(connection.getContextManager().getMetaDataContexts().getMetaData().getDatabase("foo_db")).thenReturn(null);
+ UnknownDatabaseException expected = new
UnknownDatabaseException("foo_db");
+
when(connection.getContextManager().getDatabase("foo_db")).thenThrow(expected);
+ DistSQLStatementExecutor executor = new
DistSQLStatementExecutor(connection, mock(StatementManager.class),
mock(Statement.class));
+ assertThat(assertThrows(UnknownDatabaseException.class, () ->
executor.executeQuery(createQueryContext(new ParseStatement("SELECT 1")))),
sameInstance(expected));
+ }
+
+ @Test
+ void assertExecuteUpdateWithoutDatabase() {
+ ShardingSphereConnection connection =
mock(ShardingSphereConnection.class, RETURNS_DEEP_STUBS);
+ when(connection.getCurrentDatabaseName()).thenReturn("foo_db");
+
when(connection.getContextManager().getMetaDataContexts().getMetaData().getDatabase("foo_db")).thenReturn(null);
+ UnknownDatabaseException expected = new
UnknownDatabaseException("foo_db");
+
when(connection.getContextManager().getDatabase("foo_db")).thenThrow(expected);
+ DistSQLStatementExecutor executor = new
DistSQLStatementExecutor(connection, mock(StatementManager.class),
mock(Statement.class));
+ QueryContext queryContext = createQueryContext(new
UnregisterStorageUnitStatement(Collections.singleton("foo_ds"), false, false));
+ assertThat(assertThrows(UnknownDatabaseException.class, () ->
executor.executeUpdate(queryContext)), sameInstance(expected));
+ }
+
+ @Test
+ void assertExecuteUpdateInTransaction() {
+ ShardingSphereConnection connection =
mock(ShardingSphereConnection.class, RETURNS_DEEP_STUBS);
+
when(connection.getDatabaseConnectionManager().getConnectionContext().getTransactionContext().isInTransaction()).thenReturn(true);
+ DistSQLStatementExecutor executor = new
DistSQLStatementExecutor(connection, mock(StatementManager.class),
mock(Statement.class));
+ QueryContext queryContext = createQueryContext(new
UnregisterStorageUnitStatement(Collections.singleton("foo_ds"), false, false));
+ UnsupportedSQLOperationException actual =
assertThrows(UnsupportedSQLOperationException.class, () ->
executor.executeUpdate(queryContext));
+ assertThat(actual.getMessage(), is("Unsupported SQL operation:
Non-query DistSQL is not supported within a transaction."));
+ }
+
@ParameterizedTest(name = "{0}")
@MethodSource("provideLockClusterStatements")
void assertExecuteUpdateWithLockCluster(final String name, final
LockClusterStatement sqlStatement,
@@ -132,6 +185,22 @@ class DistSQLStatementExecutorTest {
assertThrows(UnsupportedSQLOperationException.class, () ->
executor.isQuery(queryContext));
}
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("provideQueriesInTransaction")
+ void assertIsQueryInTransaction(final String name, final DistSQLStatement
sqlStatement) throws SQLException {
+ ShardingSphereConnection connection =
mock(ShardingSphereConnection.class, RETURNS_DEEP_STUBS);
+
when(connection.getDatabaseConnectionManager().getConnectionContext().getTransactionContext().isInTransaction()).thenReturn(true);
+ DistSQLStatementExecutor executor = new
DistSQLStatementExecutor(connection, mock(StatementManager.class),
mock(Statement.class));
+ assertTrue(executor.isQuery(createQueryContext(sqlStatement)));
+ }
+
+ private static Stream<Arguments> provideQueriesInTransaction() {
+ return Stream.of(
+ Arguments.of("RQL query", mock(RQLStatement.class)),
+ Arguments.of("RUL query", new ParseStatement("SELECT 1")),
+ Arguments.of("queryable RAL",
mock(QueryableRALStatement.class)));
+ }
+
private QueryContext createQueryContext(final DistSQLStatement
sqlStatement) {
QueryContext result = mock(QueryContext.class, RETURNS_DEEP_STUBS);
when(result.getSqlStatementContext().getSqlStatement()).thenReturn(sqlStatement);
diff --git
a/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/statement/ShardingSpherePreparedStatementTest.java
b/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/statement/ShardingSpherePreparedStatementTest.java
index edb4d1e7970..91d667207ae 100644
---
a/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/statement/ShardingSpherePreparedStatementTest.java
+++
b/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/statement/ShardingSpherePreparedStatementTest.java
@@ -18,7 +18,11 @@
package org.apache.shardingsphere.driver.jdbc.core.statement;
import lombok.SneakyThrows;
+import
org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import
org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
+import
org.apache.shardingsphere.database.exception.core.exception.syntax.database.UnknownDatabaseException;
+import
org.apache.shardingsphere.driver.executor.engine.facade.DriverExecutorFacade;
import
org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection;
import
org.apache.shardingsphere.driver.jdbc.core.resultset.GeneratedKeysResultSet;
import
org.apache.shardingsphere.infra.binder.context.segment.insert.keygen.GeneratedKeyContext;
@@ -27,32 +31,48 @@ import
org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
+import
org.apache.shardingsphere.infra.session.connection.transaction.TransactionConnectionContext;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.parser.rule.SQLParserRule;
import
org.apache.shardingsphere.parser.rule.builder.DefaultSQLParserRuleConfigurationBuilder;
+import
org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
+import
org.apache.shardingsphere.sql.parser.statement.core.statement.type.tcl.CommitStatement;
+import
org.apache.shardingsphere.sql.parser.statement.core.statement.type.tcl.RollbackStatement;
import org.apache.shardingsphere.sqlfederation.rule.SQLFederationRule;
import
org.apache.shardingsphere.sqlfederation.rule.builder.DefaultSQLFederationRuleConfigurationBuilder;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.MockedConstruction;
import org.mockito.internal.configuration.plugins.Plugins;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -98,6 +118,158 @@ class ShardingSpherePreparedStatementTest {
verify(generatedKeys, never()).getObject(1);
}
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("provideTransactionStates")
+ void assertExecuteUpdateAfterDatabaseRemoval(final String name, final
boolean inTransaction, final boolean exceptionOccur) throws SQLException,
ReflectiveOperationException {
+ ShardingSpherePreparedStatement preparedStatement =
createPreparedStatementWithSQLStatement(new
SQLStatement(TypedSPILoader.getService(DatabaseType.class, "SQL92")));
+ ShardingSphereConnection connection =
preparedStatement.getConnection();
+ TransactionConnectionContext transactionContext =
connection.getDatabaseConnectionManager().getConnectionContext().getTransactionContext();
+ when(transactionContext.isInTransaction()).thenReturn(inTransaction);
+ when(transactionContext.isExceptionOccur()).thenReturn(exceptionOccur);
+ SQLException expected = new SQLException("foo_failure", "foo_state",
123, new IllegalStateException("foo_cause"));
+ DriverExecutorFacade executor = mockExecutor(preparedStatement);
+ when(executor.executeUpdate(any(), any(), any(), any(), any(),
any())).thenAnswer(invocation -> {
+
when(connection.getContextManager().getMetaDataContexts().getMetaData().getDatabase("foo_db")).thenReturn(null);
+ throw expected;
+ });
+ assertThat(assertThrows(SQLException.class,
preparedStatement::executeUpdate), sameInstance(expected));
+ verify(transactionContext, never()).setExceptionOccur(anyBoolean());
+ }
+
+ private ShardingSpherePreparedStatement
createPreparedStatementWithSQLStatement(final SQLStatement sqlStatement) throws
SQLException {
+ DatabaseType databaseType = sqlStatement.getDatabaseType();
+ ShardingSphereMetaData metaData = createMetaData(databaseType);
+ SQLParserRule parserRule = mock(SQLParserRule.class,
RETURNS_DEEP_STUBS);
+ when(parserRule.getSQLParserEngine(databaseType).parse("foo_sql",
true)).thenReturn(sqlStatement);
+ SQLFederationRule federationRule =
metaData.getGlobalRuleMetaData().getSingleRule(SQLFederationRule.class);
+ when(metaData.getGlobalRuleMetaData()).thenReturn(new
RuleMetaData(Arrays.asList(parserRule, federationRule)));
+ ShardingSphereConnection connection =
mock(ShardingSphereConnection.class, RETURNS_DEEP_STUBS);
+
when(connection.getContextManager().getMetaDataContexts().getMetaData()).thenReturn(metaData);
+ when(connection.getCurrentDatabaseName()).thenReturn("foo_db");
+ ShardingSphereDatabase database = metaData.getDatabase("foo_db");
+
when(connection.getContextManager().getDatabase("foo_db")).thenReturn(database);
+ return new ShardingSpherePreparedStatement(connection, "foo_sql");
+ }
+
+ private DriverExecutorFacade mockExecutor(final
ShardingSpherePreparedStatement preparedStatement) throws
ReflectiveOperationException {
+ DriverExecutorFacade result = mock(DriverExecutorFacade.class);
+
Plugins.getMemberAccessor().set(ShardingSpherePreparedStatement.class.getDeclaredField("driverExecutorFacade"),
preparedStatement, result);
+ return result;
+ }
+
+ private static Stream<Arguments> provideTransactionStates() {
+ return Stream.of(Arguments.of("outside transaction", false, false),
Arguments.of("active transaction", true, false), Arguments.of("failed
transaction", true, true));
+ }
+
+ @Test
+ void assertExecuteUpdateWithRuntimeExceptionAfterDatabaseRemoval() throws
SQLException, ReflectiveOperationException {
+ ShardingSpherePreparedStatement preparedStatement =
createPreparedStatementWithSQLStatement(new
SQLStatement(TypedSPILoader.getService(DatabaseType.class, "SQL92")));
+ ShardingSphereConnection connection =
preparedStatement.getConnection();
+
when(connection.getDatabaseConnectionManager().getConnectionContext().getTransactionContext().isInTransaction()).thenReturn(true);
+ IllegalStateException expected = new
IllegalStateException("foo_failure");
+ DriverExecutorFacade executor = mockExecutor(preparedStatement);
+ when(executor.executeUpdate(any(), any(), any(), any(), any(),
any())).thenAnswer(invocation -> {
+
when(connection.getContextManager().getMetaDataContexts().getMetaData().getDatabase("foo_db")).thenReturn(null);
+ throw expected;
+ });
+ assertThat(assertThrows(SQLException.class,
preparedStatement::executeUpdate).getCause(), sameInstance(expected));
+ }
+
+ @Test
+ void assertExecuteUpdateWhenFailedTransactionDatabaseIsMissing() throws
SQLException {
+ ShardingSpherePreparedStatement preparedStatement =
createPreparedStatementWithSQLStatement(new
SQLStatement(TypedSPILoader.getService(DatabaseType.class, "SQL92")));
+ ShardingSphereConnection connection =
preparedStatement.getConnection();
+
when(connection.getDatabaseConnectionManager().getConnectionContext().getTransactionContext().isExceptionOccur()).thenReturn(true);
+
when(connection.getContextManager().getMetaDataContexts().getMetaData().getDatabase("foo_db")).thenReturn(null);
+ UnknownDatabaseException expected = new
UnknownDatabaseException("foo_db");
+
when(connection.getContextManager().getDatabase("foo_db")).thenThrow(expected);
+ assertThat(assertThrows(SQLException.class,
preparedStatement::executeUpdate).getCause(), sameInstance(expected));
+ }
+
+ @Test
+ void assertExecuteUpdateWhenPostgreSQLTransactionFailed() throws
SQLException {
+ DatabaseType databaseType =
TypedSPILoader.getService(DatabaseType.class, "PostgreSQL");
+ ShardingSpherePreparedStatement preparedStatement =
createPreparedStatementWithSQLStatement(new SQLStatement(databaseType));
+ ShardingSphereConnection connection =
preparedStatement.getConnection();
+
when(connection.getDatabaseConnectionManager().getConnectionContext().getTransactionContext().isExceptionOccur()).thenReturn(true);
+
when(connection.getContextManager().getMetaDataContexts().getMetaData().getProtocolType()).thenReturn(TypedSPILoader.getService(DatabaseType.class,
"MySQL"));
+ DialectDatabaseMetaData dialectDatabaseMetaData =
mock(DialectDatabaseMetaData.class, RETURNS_DEEP_STUBS);
+
when(dialectDatabaseMetaData.getTransactionOption().isAllowCommitAndRollbackOnlyWhenTransactionFailed()).thenReturn(true);
+ AtomicReference<DatabaseType> actualProtocolType = new
AtomicReference<>();
+ try (MockedConstruction<DatabaseTypeRegistry> ignored =
mockConstruction(DatabaseTypeRegistry.class, (mock, context) -> {
+ actualProtocolType.set((DatabaseType) context.arguments().get(0));
+
when(mock.getDialectDatabaseMetaData()).thenReturn(dialectDatabaseMetaData);
+ })) {
+ SQLException actual =
assertThrows(SQLFeatureNotSupportedException.class,
preparedStatement::executeUpdate);
+ assertThat(actual.getMessage(), is("Current transaction is
aborted, commands ignored until end of transaction block."));
+ assertThat(actualProtocolType.get(), sameInstance(databaseType));
+ }
+ }
+
+ @Test
+ void assertExecuteUpdateCommitWhenPostgreSQLTransactionFailed() throws
SQLException, ReflectiveOperationException {
+ ShardingSpherePreparedStatement preparedStatement =
createPreparedStatementWithSQLStatement(new
CommitStatement(TypedSPILoader.getService(DatabaseType.class, "PostgreSQL")));
+
when(preparedStatement.getConnection().getDatabaseConnectionManager().getConnectionContext().getTransactionContext().isExceptionOccur()).thenReturn(true);
+ mockExecutor(preparedStatement);
+ try (MockedConstruction<DatabaseTypeRegistry> ignored =
mockFailedTransactionOption()) {
+ assertThat(preparedStatement.executeUpdate(), is(0));
+ }
+ }
+
+ private MockedConstruction<DatabaseTypeRegistry>
mockFailedTransactionOption() {
+ DialectDatabaseMetaData dialectDatabaseMetaData =
mock(DialectDatabaseMetaData.class, RETURNS_DEEP_STUBS);
+
when(dialectDatabaseMetaData.getTransactionOption().isAllowCommitAndRollbackOnlyWhenTransactionFailed()).thenReturn(true);
+ return mockConstruction(DatabaseTypeRegistry.class, (mock, context) ->
when(mock.getDialectDatabaseMetaData()).thenReturn(dialectDatabaseMetaData));
+ }
+
+ @Test
+ void assertExecuteUpdateRollbackWhenPostgreSQLTransactionFailed() throws
SQLException, ReflectiveOperationException {
+ ShardingSpherePreparedStatement preparedStatement =
createPreparedStatementWithSQLStatement(new
RollbackStatement(TypedSPILoader.getService(DatabaseType.class, "PostgreSQL")));
+
when(preparedStatement.getConnection().getDatabaseConnectionManager().getConnectionContext().getTransactionContext().isExceptionOccur()).thenReturn(true);
+ mockExecutor(preparedStatement);
+ try (MockedConstruction<DatabaseTypeRegistry> ignored =
mockFailedTransactionOption()) {
+ assertThat(preparedStatement.executeUpdate(), is(0));
+ }
+ }
+
+ @Test
+ void assertExecuteUpdateWhenMySQLTransactionFailed() throws SQLException,
ReflectiveOperationException {
+ ShardingSpherePreparedStatement preparedStatement =
createPreparedStatementWithSQLStatement(new
SQLStatement(TypedSPILoader.getService(DatabaseType.class, "MySQL")));
+
when(preparedStatement.getConnection().getDatabaseConnectionManager().getConnectionContext().getTransactionContext().isExceptionOccur()).thenReturn(true);
+ mockExecutor(preparedStatement);
+ assertThat(preparedStatement.executeUpdate(), is(0));
+ }
+
+ @Test
+ void assertExecuteUpdateMarksPostgreSQLTransactionFailed() throws
SQLException, ReflectiveOperationException {
+ ShardingSpherePreparedStatement preparedStatement =
createPreparedStatementWithSQLStatement(new
SQLStatement(TypedSPILoader.getService(DatabaseType.class, "PostgreSQL")));
+ TransactionConnectionContext transactionContext =
preparedStatement.getConnection().getDatabaseConnectionManager().getConnectionContext().getTransactionContext();
+ when(transactionContext.isInTransaction()).thenReturn(true);
+ DriverExecutorFacade executor = mockExecutor(preparedStatement);
+ SQLException expected = new SQLException("foo_failure");
+ when(executor.executeUpdate(any(), any(), any(), any(), any(),
any())).thenThrow(expected);
+ DialectDatabaseMetaData dialectDatabaseMetaData =
mock(DialectDatabaseMetaData.class, RETURNS_DEEP_STUBS);
+
when(dialectDatabaseMetaData.getSchemaOption().getDefaultSchema()).thenReturn(Optional.of("public"));
+ try (
+ MockedConstruction<DatabaseTypeRegistry> ignored =
mockConstruction(DatabaseTypeRegistry.class,
+ (mock, context) ->
when(mock.getDialectDatabaseMetaData()).thenReturn(dialectDatabaseMetaData))) {
+ assertThat(assertThrows(SQLException.class,
preparedStatement::executeUpdate), sameInstance(expected));
+ verify(transactionContext).setExceptionOccur(true);
+ }
+ }
+
+ @Test
+ void assertExecuteUpdateKeepsMySQLTransactionState() throws SQLException,
ReflectiveOperationException {
+ ShardingSpherePreparedStatement preparedStatement =
createPreparedStatementWithSQLStatement(new
SQLStatement(TypedSPILoader.getService(DatabaseType.class, "MySQL")));
+ TransactionConnectionContext transactionContext =
preparedStatement.getConnection().getDatabaseConnectionManager().getConnectionContext().getTransactionContext();
+ when(transactionContext.isInTransaction()).thenReturn(true);
+ DriverExecutorFacade executor = mockExecutor(preparedStatement);
+ SQLException expected = new SQLException("foo_failure");
+ when(executor.executeUpdate(any(), any(), any(), any(), any(),
any())).thenThrow(expected);
+ assertThat(assertThrows(SQLException.class,
preparedStatement::executeUpdate), sameInstance(expected));
+ verify(transactionContext, never()).setExceptionOccur(anyBoolean());
+ }
+
private ShardingSpherePreparedStatement createPreparedStatement() throws
SQLException {
return
createPreparedStatement(TypedSPILoader.getService(DatabaseType.class, "SQL92"));
}
diff --git
a/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/statement/ShardingSphereStatementTest.java
b/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/statement/ShardingSphereStatementTest.java
index 65269aba096..f86742f03a3 100644
---
a/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/statement/ShardingSphereStatementTest.java
+++
b/jdbc/src/test/java/org/apache/shardingsphere/driver/jdbc/core/statement/ShardingSphereStatementTest.java
@@ -19,10 +19,17 @@ package
org.apache.shardingsphere.driver.jdbc.core.statement;
import lombok.SneakyThrows;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import
org.apache.shardingsphere.database.exception.core.SQLExceptionTransformEngine;
+import
org.apache.shardingsphere.database.exception.core.exception.protocol.DatabaseProtocolException;
+import
org.apache.shardingsphere.database.exception.core.exception.syntax.database.NoDatabaseSelectedException;
+import
org.apache.shardingsphere.database.exception.core.exception.syntax.database.UnknownDatabaseException;
+import org.apache.shardingsphere.distsql.statement.type.rul.sql.ParseStatement;
import
org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection;
import
org.apache.shardingsphere.infra.binder.context.segment.insert.keygen.GeneratedKeyContext;
+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.config.props.ConfigurationProperties;
+import
org.apache.shardingsphere.infra.exception.external.sql.ShardingSphereSQLException;
import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData;
import
org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase;
import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
@@ -33,6 +40,10 @@ import
org.apache.shardingsphere.parser.rule.builder.DefaultSQLParserRuleConfigu
import org.apache.shardingsphere.sqlfederation.rule.SQLFederationRule;
import
org.apache.shardingsphere.sqlfederation.rule.builder.DefaultSQLFederationRuleConfigurationBuilder;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.MockedStatic;
import org.mockito.internal.configuration.plugins.Plugins;
import java.lang.reflect.Field;
@@ -44,12 +55,21 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.Properties;
+import java.util.stream.Stream;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -90,12 +110,216 @@ class ShardingSphereStatementTest {
verify(generatedKeys, never()).getObject(1);
}
+ @Test
+ void assertCreateStatementAfterDatabaseRemoved() {
+ ShardingSphereConnection connection =
createConnection(createMetaData(TypedSPILoader.getService(DatabaseType.class,
"MySQL")));
+ UnknownDatabaseException expected = removeDatabase(connection);
+ assertThat(assertThrows(UnknownDatabaseException.class, () -> new
ShardingSphereStatement(connection)), sameInstance(expected));
+ verify(connection, never()).registerStatementManager(any());
+ }
+
+ private UnknownDatabaseException removeDatabase(final
ShardingSphereConnection connection) {
+ UnknownDatabaseException result = new
UnknownDatabaseException("foo_db");
+
when(connection.getContextManager().getMetaDataContexts().getMetaData().getDatabase("foo_db")).thenReturn(null);
+
when(connection.getContextManager().getDatabase("foo_db")).thenThrow(result);
+ return result;
+ }
+
+ @Test
+ void assertGetGeneratedKeysAfterDatabaseRemoved() {
+ ShardingSphereStatement statement =
createStatement(TypedSPILoader.getService(DatabaseType.class, "MySQL"));
+ setInsertStatementContext(statement);
+ UnknownDatabaseException expected =
removeDatabase(statement.getConnection());
+ assertThat(assertThrows(UnknownDatabaseException.class,
statement::getGeneratedKeys), sameInstance(expected));
+ }
+
+ @Test
+ void assertGetGeneratedKeysWithoutColumnName() throws SQLException,
ReflectiveOperationException {
+ ShardingSphereStatement statement =
createStatement(TypedSPILoader.getService(DatabaseType.class, "SQL92"));
+ QueryContext queryContext = mock(QueryContext.class);
+
when(queryContext.getSqlStatementContext()).thenReturn(mock(SQLStatementContext.class));
+
Plugins.getMemberAccessor().set(ShardingSphereStatement.class.getDeclaredField("queryContext"),
statement, queryContext);
+ try (ResultSet actual = statement.getGeneratedKeys()) {
+ assertNull(actual.getMetaData().getColumnName(1));
+ assertFalse(actual.next());
+ }
+ }
+
+ @Test
+ void assertGetGeneratedKeysWithDifferentGlobalProtocol() throws
SQLException {
+ ShardingSphereStatement statement =
createStatement(TypedSPILoader.getService(DatabaseType.class, "MySQL"));
+
when(statement.getConnection().getContextManager().getMetaDataContexts().getMetaData().getProtocolType()).thenReturn(TypedSPILoader.getService(DatabaseType.class,
"PostgreSQL"));
+ setInsertStatementContext(statement);
+ try (ResultSet actual = statement.getGeneratedKeys()) {
+ assertThat(actual.getMetaData().getColumnName(1),
is("GENERATED_KEY"));
+ assertFalse(actual.next());
+ }
+ }
+
+ @Test
+ void assertGetGeneratedKeysWithBackendFailure() throws SQLException {
+ ShardingSphereStatement statement =
createStatement(TypedSPILoader.getService(DatabaseType.class, "MySQL"));
+ setInsertStatementContext(statement);
+ Statement routedStatement = mock(Statement.class);
+ SQLException expected = new SQLException("foo_failure", "42000", 123);
+ when(routedStatement.getGeneratedKeys()).thenThrow(expected);
+ statement.getRoutedStatements().add(routedStatement);
+ assertThat(assertThrows(SQLException.class,
statement::getGeneratedKeys), sameInstance(expected));
+ }
+
+ @Test
+ void assertExecuteQueryWithDatabaseRemovedDuringFailure() throws
SQLException {
+ SQLException expected = new SQLException("foo_failure", "42000", 123);
+ ShardingSphereStatement statement = createFailingStatement(expected);
+ assertThat(assertThrows(SQLException.class, () ->
statement.executeQuery("SELECT 1")), sameInstance(expected));
+ }
+
+ private ShardingSphereStatement createFailingStatement(final Exception
cause) throws SQLException {
+ ShardingSphereStatement result =
createStatement(TypedSPILoader.getService(DatabaseType.class, "MySQL"));
+ Statement routedStatement = mock(Statement.class);
+ doAnswer(invocation -> {
+ removeDatabase(result.getConnection());
+ throw cause;
+ }).when(routedStatement).close();
+ getStatements(result).add(routedStatement);
+ return result;
+ }
+
+ @Test
+ void assertExecuteUpdateWithDatabaseRemovedDuringFailure() throws
SQLException {
+ SQLException expected = new SQLException("foo_failure", "42000", 123);
+ ShardingSphereStatement statement = createFailingStatement(expected);
+ assertThat(assertThrows(SQLException.class, () ->
statement.executeUpdate("SELECT 1")), sameInstance(expected));
+ }
+
+ @Test
+ void assertExecuteUpdateWithGeneratedKeysDuringDatabaseRemoval() throws
SQLException {
+ SQLException expected = new SQLException("foo_failure");
+ ShardingSphereStatement statement = createFailingStatement(expected);
+ assertThat(assertThrows(SQLException.class, () ->
statement.executeUpdate("SELECT 1", Statement.RETURN_GENERATED_KEYS)),
sameInstance(expected));
+ }
+
+ @Test
+ void assertExecuteUpdateWithColumnIndexesDuringDatabaseRemoval() throws
SQLException {
+ SQLException expected = new SQLException("foo_failure");
+ ShardingSphereStatement statement = createFailingStatement(expected);
+ assertThat(assertThrows(SQLException.class, () ->
statement.executeUpdate("SELECT 1", new int[]{1})), sameInstance(expected));
+ }
+
+ @Test
+ void assertExecuteUpdateWithColumnNamesDuringDatabaseRemoval() throws
SQLException {
+ SQLException expected = new SQLException("foo_failure");
+ ShardingSphereStatement statement = createFailingStatement(expected);
+ assertThat(assertThrows(SQLException.class, () ->
statement.executeUpdate("SELECT 1", new String[]{"id"})),
sameInstance(expected));
+ }
+
+ @Test
+ void assertExecuteWithDatabaseRemovedDuringFailure() throws SQLException {
+ SQLException expected = new SQLException("foo_failure", "42000", 123);
+ ShardingSphereStatement statement = createFailingStatement(expected);
+ assertThat(assertThrows(SQLException.class, () ->
statement.execute("SELECT 1")), sameInstance(expected));
+ }
+
+ @Test
+ void assertExecuteWithGeneratedKeysDuringDatabaseRemoval() throws
SQLException {
+ SQLException expected = new SQLException("foo_failure");
+ ShardingSphereStatement statement = createFailingStatement(expected);
+ assertThat(assertThrows(SQLException.class, () ->
statement.execute("SELECT 1", Statement.RETURN_GENERATED_KEYS)),
sameInstance(expected));
+ }
+
+ @Test
+ void assertExecuteWithColumnIndexesDuringDatabaseRemoval() throws
SQLException {
+ SQLException expected = new SQLException("foo_failure");
+ ShardingSphereStatement statement = createFailingStatement(expected);
+ assertThat(assertThrows(SQLException.class, () ->
statement.execute("SELECT 1", new int[]{1})), sameInstance(expected));
+ }
+
+ @Test
+ void assertExecuteWithColumnNamesDuringDatabaseRemoval() throws
SQLException {
+ SQLException expected = new SQLException("foo_failure");
+ ShardingSphereStatement statement = createFailingStatement(expected);
+ assertThat(assertThrows(SQLException.class, () ->
statement.execute("SELECT 1", new String[]{"id"})), sameInstance(expected));
+ }
+
+ @Test
+ void assertExecuteDistSQLDuringDatabaseRemoval() {
+ DatabaseType databaseType =
TypedSPILoader.getService(DatabaseType.class, "MySQL");
+ ShardingSphereStatement statement = createStatement(databaseType);
+ ShardingSphereConnection connection = statement.getConnection();
+ ShardingSphereMetaData metaData =
connection.getContextManager().getMetaDataContexts().getMetaData();
+ SQLParserRule parserRule = mock(SQLParserRule.class,
RETURNS_DEEP_STUBS);
+ RuleMetaData globalRuleMetaData = new
RuleMetaData(Collections.singletonList(parserRule));
+ when(metaData.getGlobalRuleMetaData()).thenReturn(globalRuleMetaData);
+ ParseStatement sqlStatement = new ParseStatement("SELECT 1");
+ UnknownDatabaseException expected = new
UnknownDatabaseException("foo_db");
+ when(parserRule.getSQLParserEngine(databaseType).parse("foo_distsql",
false)).thenAnswer(invocation -> {
+ when(metaData.getDatabase("foo_db")).thenReturn(null);
+
when(connection.getContextManager().getDatabase("foo_db")).thenThrow(expected);
+ return sqlStatement;
+ });
+ SQLException actual = assertThrows(SQLException.class, () ->
statement.execute("foo_distsql"));
+ assertThat(actual.getCause(), sameInstance(expected));
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("provideFailuresDuringDatabaseRemoval")
+ void assertExecuteUpdateWithRuntimeFailureDuringDatabaseRemoval(final
String name, final RuntimeException expected) throws SQLException {
+ ShardingSphereStatement statement = createFailingStatement(expected);
+ SQLException actual = assertThrows(SQLException.class, () ->
statement.executeUpdate("SELECT 1"));
+ assertThat(actual.getCause(), sameInstance(expected));
+ }
+
+ private static Stream<Arguments> provideFailuresDuringDatabaseRemoval() {
+ return Stream.of(
+ Arguments.of("runtime failure", new
IllegalStateException("foo_failure")),
+ Arguments.of("database missing", new
UnknownDatabaseException("foo_db")),
+ Arguments.of("database not selected", new
NoDatabaseSelectedException()));
+ }
+
+ @Test
+ void assertExecuteUpdateWithProtocolFailureDuringDatabaseRemoval() throws
SQLException {
+ ShardingSphereStatement statement = createFailingStatement(new
DatabaseProtocolException("foo_failure"));
+ SQLException actual = assertThrows(SQLException.class, () ->
statement.executeUpdate("SELECT 1"));
+ assertThat(actual.getErrorCode(), is(30002));
+ assertThat(actual.getMessage(), is("Database protocol exception:
foo_failure"));
+ }
+
+ @Test
+ void assertExecuteUpdateWithSqlFailureDuringDatabaseRemoval() throws
SQLException {
+ ShardingSphereSQLException cause =
mock(ShardingSphereSQLException.class);
+ SQLException expected = new SQLException("foo_failure", "42000", 123);
+ when(cause.toSQLException()).thenReturn(expected);
+ ShardingSphereStatement statement = createFailingStatement(cause);
+ assertThat(assertThrows(SQLException.class, () ->
statement.executeUpdate("SELECT 1")), sameInstance(expected));
+ }
+
+ @Test
+ void assertExecuteUpdateWithDifferentGlobalProtocol() throws SQLException {
+ DatabaseType databaseType =
TypedSPILoader.getService(DatabaseType.class, "MySQL");
+ ShardingSphereStatement statement = createStatement(databaseType);
+
when(statement.getConnection().getContextManager().getMetaDataContexts().getMetaData().getProtocolType()).thenReturn(TypedSPILoader.getService(DatabaseType.class,
"PostgreSQL"));
+ Statement routedStatement = mock(Statement.class);
+ UnknownDatabaseException cause = new
UnknownDatabaseException("foo_db");
+ doThrow(cause).when(routedStatement).close();
+ statement.getRoutedStatements().add(routedStatement);
+ SQLException expected = new SQLException("foo_failure");
+ try (MockedStatic<SQLExceptionTransformEngine> mockedEngine =
mockStatic(SQLExceptionTransformEngine.class)) {
+ mockedEngine.when(() ->
SQLExceptionTransformEngine.toSQLException(cause,
databaseType)).thenReturn(expected);
+ assertThat(assertThrows(SQLException.class, () ->
statement.executeUpdate("SELECT 1")), sameInstance(expected));
+ }
+ }
+
private ShardingSphereStatement createStatement(final DatabaseType
databaseType) {
- ShardingSphereMetaData metaData = createMetaData(databaseType);
- ShardingSphereConnection connection =
mock(ShardingSphereConnection.class, RETURNS_DEEP_STUBS);
-
when(connection.getContextManager().getMetaDataContexts().getMetaData()).thenReturn(metaData);
- when(connection.getCurrentDatabaseName()).thenReturn("foo_db");
- return new ShardingSphereStatement(connection);
+ return new
ShardingSphereStatement(createConnection(createMetaData(databaseType)));
+ }
+
+ private ShardingSphereConnection createConnection(final
ShardingSphereMetaData metaData) {
+ ShardingSphereConnection result = mock(ShardingSphereConnection.class,
RETURNS_DEEP_STUBS);
+
when(result.getContextManager().getMetaDataContexts().getMetaData()).thenReturn(metaData);
+ when(result.getCurrentDatabaseName()).thenReturn("foo_db");
+ ShardingSphereDatabase database = metaData.getDatabase("foo_db");
+
when(result.getContextManager().getDatabase("foo_db")).thenReturn(database);
+ return result;
}
private ShardingSphereMetaData createMetaData(final DatabaseType
databaseType) {
@@ -104,6 +328,7 @@ class ShardingSphereStatementTest {
when(database.getRuleMetaData()).thenReturn(new
RuleMetaData(Collections.emptyList()));
ShardingSphereMetaData result = mock(ShardingSphereMetaData.class);
when(result.getDatabase("foo_db")).thenReturn(database);
+ when(result.getProtocolType()).thenReturn(databaseType);
when(result.getGlobalRuleMetaData()).thenReturn(new
RuleMetaData(Arrays.asList(
new SQLParserRule(new
DefaultSQLParserRuleConfigurationBuilder().build()),
new SQLFederationRule(new
DefaultSQLFederationRuleConfigurationBuilder().build(),
Collections.emptyList()))));