You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by zh...@apache.org on 2026/08/10 15:22:01 UTC
(shardingsphere) branch master updated: Improve error and exception handling in Firebird Proxy (#38927)
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 6fe074fd8c5 Improve error and exception handling in Firebird Proxy (#38927)
6fe074fd8c5 is described below
commit 6fe074fd8c5345b21aeb3d3fdfd63800772009b7
Author: Maxim Sentyabrskiy <16...@users.noreply.github.com>
AuthorDate: Mon Aug 10 18:21:55 2026 +0300
Improve error and exception handling in Firebird Proxy (#38927)
* handle codec errors with response packets
* add InvalidBatchHandleException for batch statement handle
* add TABLE_EXISTS exception
* add COLUMN_UNKNOWN exception
* remove unreachable column unknown mapping
* refactor and add InvalidBlobHandle Exception
* add DYNAMIC_SQL_ERROR exception
* add CHARSET_NOT_FOUND exception
* add InvalidStatementHandle and InvalidTransactionHandle exception
* add exceptions
* map invalid attach lc_ctype to CHARSET_NOT_FOUND
* support CREATE DATABASE and DROP DATABASE
* add Union SQL
* added error handling for batch
* checkstyle fix
* checkstyle fix
* revert blob error handling
* fix CREATE/DROP DATABASE
* add union all test cases
* fix transaction handle validation
* fix ci
* fix concurrent transaction start
* fix batch failure mapping
* fix batch too big reset
* fix database DDL execution
* fix union combine type and trailing order by
* fix batch create error identities
* checkstyle spotless fix
* fix firebird union order by test to use ordinal
* fix batch partial failure reporting
* fix batch error mode and transaction handle validation
* fix review
* fix checkstyle/spotless
* fix spotless
---
.../protocol/BatchAlreadyOpenedException.java} | 21 +-
.../BatchParametersRequiredException.java} | 21 +-
.../protocol/BatchTooBigException.java} | 21 +-
.../protocol/ExcessTransactionsException.java} | 21 +-
.../protocol/InvalidBatchHandleException.java} | 21 +-
.../InvalidBatchMessageFormatException.java} | 21 +-
.../InvalidBatchParameterVersionException.java} | 24 +-
.../protocol/InvalidStatementHandleException.java} | 21 +-
.../InvalidTransactionHandleException.java} | 21 +-
.../mapper/FirebirdDialectExceptionMapper.java | 58 ++++
.../exception/firebird/sqlstate/FirebirdState.java | 18 +-
.../firebird/vendor/FirebirdVendorError.java | 37 ++-
.../protocol/BatchAlreadyOpenedExceptionTest.java} | 27 +-
.../BatchParametersRequiredExceptionTest.java} | 27 +-
.../protocol/BatchTooBigExceptionTest.java} | 27 +-
.../protocol/ExcessTransactionsExceptionTest.java} | 27 +-
.../protocol/InvalidBatchHandleExceptionTest.java} | 27 +-
.../InvalidBatchMessageFormatExceptionTest.java} | 27 +-
...InvalidBatchParameterVersionExceptionTest.java} | 28 +-
.../InvalidStatementHandleExceptionTest.java} | 27 +-
.../InvalidTransactionHandleExceptionTest.java} | 27 +-
.../mapper/FirebirdDialectExceptionMapperTest.java | 85 ++++++
.../firebird/codec/FirebirdPacketCodecEngine.java | 28 +-
.../firebird/err/FirebirdErrorPacketFactory.java | 2 +-
.../firebird/err/FirebirdErrorSegmentResolver.java | 122 ++++++++
.../firebird/err/FirebirdStatusVector.java | 53 +++-
.../batch/FirebirdBatchMessageCommandPacket.java | 5 +-
.../query/batch/FirebirdBatchStatement.java | 6 +-
.../protocol/util/FirebirdDateTimeUtils.java | 6 +-
.../FirebirdBatchCompletionStateResponse.java | 53 +++-
.../codec/FirebirdPacketCodecEngineTest.java | 108 ++++++-
.../err/FirebirdErrorPacketFactoryTest.java | 18 +-
.../err/FirebirdErrorSegmentResolverTest.java | 100 +++++++
.../firebird/err/FirebirdStatusVectorTest.java | 67 ++++-
.../batch/FirebirdBatchCommandPacketTest.java | 4 +-
.../query/batch/FirebirdBatchStatementTest.java | 11 +-
.../FirebirdBatchCompletionStateResponseTest.java | 13 +
.../main/antlr4/imports/firebird/DDLStatement.g4 | 4 +-
.../type/FirebirdDDLStatementVisitor.java | 22 ++
.../type/FirebirdDMLStatementVisitor.java | 47 ++-
.../charset/FirebirdCharsetVariableProvider.java | 2 +-
.../FirebirdAuthenticationEngine.java | 15 +-
.../command/FirebirdCommandExecuteEngine.java | 2 +-
.../query/batch/FirebirdBatchCompletion.java | 37 ++-
.../batch/FirebirdBatchCreateCommandExecutor.java | 35 ++-
.../batch/FirebirdBatchExecuteCommandExecutor.java | 42 ++-
.../batch/FirebirdBatchMessageCommandExecutor.java | 8 +-
.../batch/FirebirdBatchedStatementsExecutor.java | 328 ++++++++++++++++++---
.../FirebirdExecuteStatementCommandExecutor.java | 13 +
.../FirebirdPrepareStatementCommandExecutor.java | 14 +-
.../FirebirdCommitTransactionCommandExecutor.java | 10 +
...FirebirdRollbackTransactionCommandExecutor.java | 10 +
.../FirebirdStartTransactionCommandExecutor.java | 10 +
.../FirebirdTransactionIdGenerator.java | 48 ++-
.../FirebirdAuthenticationEngineTest.java | 47 +++
.../command/FirebirdCommandExecuteEngineTest.java | 2 +-
.../FirebirdBatchCreateCommandExecutorTest.java | 55 +++-
.../FirebirdBatchExecuteCommandExecutorTest.java | 107 ++++++-
.../FirebirdBatchMessageCommandExecutorTest.java | 50 +++-
.../FirebirdBatchedStatementsExecutorTest.java | 328 ++++++++++++++++++++-
...irebirdExecuteStatementCommandExecutorTest.java | 27 ++
...irebirdPrepareStatementCommandExecutorTest.java | 47 +++
...rebirdCommitTransactionCommandExecutorTest.java | 51 ++++
...birdRollbackTransactionCommandExecutorTest.java | 51 ++++
...irebirdStartTransactionCommandExecutorTest.java | 23 ++
.../FirebirdTransactionIdGeneratorTest.java | 25 ++
.../main/resources/case/ddl/create-database.xml | 4 +
.../src/main/resources/case/dml/select-combine.xml | 112 +++++++
.../parser/src/main/resources/case/dml/select.xml | 10 -
.../sql/supported/ddl/create-database.xml | 4 +
.../resources/sql/supported/ddl/drop-database.xml | 2 +-
.../resources/sql/supported/dml/select-combine.xml | 4 +
.../main/resources/sql/supported/dml/select.xml | 1 -
73 files changed, 2411 insertions(+), 416 deletions(-)
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchAlreadyOpenedException.java
similarity index 57%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchAlreadyOpenedException.java
index f00a726c59e..64b55fe8678 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchAlreadyOpenedException.java
@@ -15,22 +15,23 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.apache.shardingsphere.database.exception.core.exception.SQLDialectException;
/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
+ * Batch already opened exception for Firebird.
*/
-@RequiredArgsConstructor
@Getter
-public enum FirebirdState implements SQLState {
+public final class BatchAlreadyOpenedException extends SQLDialectException {
+
+ private static final long serialVersionUID = 5128401254291859032L;
- UNAVAILABLE_DATABASE("08001");
+ private final int statementHandle;
- private final String value;
+ public BatchAlreadyOpenedException(final int statementHandle) {
+ super(String.format("Batch already opened for statement handle %d", statementHandle));
+ this.statementHandle = statementHandle;
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchParametersRequiredException.java
similarity index 58%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchParametersRequiredException.java
index f00a726c59e..0d284289a7d 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchParametersRequiredException.java
@@ -15,22 +15,23 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.apache.shardingsphere.database.exception.core.exception.SQLDialectException;
/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
+ * Batch parameters required exception for Firebird.
*/
-@RequiredArgsConstructor
@Getter
-public enum FirebirdState implements SQLState {
+public final class BatchParametersRequiredException extends SQLDialectException {
+
+ private static final long serialVersionUID = 6103752548913834517L;
- UNAVAILABLE_DATABASE("08001");
+ private final int statementHandle;
- private final String value;
+ public BatchParametersRequiredException(final int statementHandle) {
+ super("Statement used in batch must have parameters");
+ this.statementHandle = statementHandle;
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchTooBigException.java
similarity index 53%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchTooBigException.java
index f00a726c59e..fdb669f591c 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchTooBigException.java
@@ -15,22 +15,23 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.apache.shardingsphere.database.exception.core.exception.SQLDialectException;
/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
+ * Batch too big exception for Firebird.
*/
-@RequiredArgsConstructor
@Getter
-public enum FirebirdState implements SQLState {
+public final class BatchTooBigException extends SQLDialectException {
+
+ private static final long serialVersionUID = 8934524677490596556L;
- UNAVAILABLE_DATABASE("08001");
+ private final int statementHandle;
- private final String value;
+ public BatchTooBigException(final int statementHandle, final long accumulatedSize, final long incomingSize, final long bufferSize) {
+ super(String.format("Batch is too big: accumulated %d + incoming %d data bytes exceeds buffer size limit %d bytes", accumulatedSize, incomingSize, bufferSize));
+ this.statementHandle = statementHandle;
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/ExcessTransactionsException.java
similarity index 57%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/ExcessTransactionsException.java
index f00a726c59e..cae4fd71ded 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/ExcessTransactionsException.java
@@ -15,22 +15,23 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.apache.shardingsphere.database.exception.core.exception.SQLDialectException;
/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
+ * Excess transactions exception for Firebird.
*/
-@RequiredArgsConstructor
@Getter
-public enum FirebirdState implements SQLState {
+public final class ExcessTransactionsException extends SQLDialectException {
+
+ private static final long serialVersionUID = 2831574758434100827L;
- UNAVAILABLE_DATABASE("08001");
+ private final int maxTransactions;
- private final String value;
+ public ExcessTransactionsException(final int maxTransactions) {
+ super(String.format("Attempt to start more than %d transactions", maxTransactions));
+ this.maxTransactions = maxTransactions;
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchHandleException.java
similarity index 57%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchHandleException.java
index f00a726c59e..f72291a6a46 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchHandleException.java
@@ -15,22 +15,23 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.apache.shardingsphere.database.exception.core.exception.SQLDialectException;
/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
+ * Invalid batch handle exception for Firebird.
*/
-@RequiredArgsConstructor
@Getter
-public enum FirebirdState implements SQLState {
+public final class InvalidBatchHandleException extends SQLDialectException {
+
+ private static final long serialVersionUID = 1209066053518706039L;
- UNAVAILABLE_DATABASE("08001");
+ private final int statementHandle;
- private final String value;
+ public InvalidBatchHandleException(final int statementHandle) {
+ super(String.format("Invalid batch handle: statement handle %d", statementHandle));
+ this.statementHandle = statementHandle;
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchMessageFormatException.java
similarity index 58%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchMessageFormatException.java
index f00a726c59e..8b0853d6ccd 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchMessageFormatException.java
@@ -15,22 +15,23 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.apache.shardingsphere.database.exception.core.exception.SQLDialectException;
/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
+ * Invalid batch message format exception for Firebird.
*/
-@RequiredArgsConstructor
@Getter
-public enum FirebirdState implements SQLState {
+public final class InvalidBatchMessageFormatException extends SQLDialectException {
+
+ private static final long serialVersionUID = 8294120114147655351L;
- UNAVAILABLE_DATABASE("08001");
+ private final String detail;
- private final String value;
+ public InvalidBatchMessageFormatException(final String detail) {
+ super(String.format("Invalid batch message format: %s", detail));
+ this.detail = detail;
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchParameterVersionException.java
similarity index 52%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchParameterVersionException.java
index f00a726c59e..a061b33d694 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchParameterVersionException.java
@@ -15,22 +15,26 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.apache.shardingsphere.database.exception.core.exception.SQLDialectException;
/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
+ * Invalid batch parameter version exception for Firebird.
*/
-@RequiredArgsConstructor
@Getter
-public enum FirebirdState implements SQLState {
+public final class InvalidBatchParameterVersionException extends SQLDialectException {
+
+ private static final long serialVersionUID = -3092445176872728093L;
+
+ private final int version;
- UNAVAILABLE_DATABASE("08001");
+ private final int expectedVersion;
- private final String value;
+ public InvalidBatchParameterVersionException(final int version, final int expectedVersion) {
+ super(String.format("Wrong version of batch parameters block %d, should be %d", version, expectedVersion));
+ this.version = version;
+ this.expectedVersion = expectedVersion;
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidStatementHandleException.java
similarity index 57%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidStatementHandleException.java
index f00a726c59e..9fd288f9be1 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidStatementHandleException.java
@@ -15,22 +15,23 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.apache.shardingsphere.database.exception.core.exception.SQLDialectException;
/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
+ * Invalid statement handle exception for Firebird.
*/
-@RequiredArgsConstructor
@Getter
-public enum FirebirdState implements SQLState {
+public final class InvalidStatementHandleException extends SQLDialectException {
+
+ private static final long serialVersionUID = 1643157744200032161L;
- UNAVAILABLE_DATABASE("08001");
+ private final int statementHandle;
- private final String value;
+ public InvalidStatementHandleException(final int statementHandle) {
+ super(String.format("Invalid statement handle: %d", statementHandle));
+ this.statementHandle = statementHandle;
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidTransactionHandleException.java
similarity index 57%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidTransactionHandleException.java
index f00a726c59e..770fbc059f2 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidTransactionHandleException.java
@@ -15,22 +15,23 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.apache.shardingsphere.database.exception.core.exception.SQLDialectException;
/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
+ * Invalid transaction handle exception for Firebird.
*/
-@RequiredArgsConstructor
@Getter
-public enum FirebirdState implements SQLState {
+public final class InvalidTransactionHandleException extends SQLDialectException {
+
+ private static final long serialVersionUID = -1877813444031489651L;
- UNAVAILABLE_DATABASE("08001");
+ private final int transactionHandle;
- private final String value;
+ public InvalidTransactionHandleException(final int transactionHandle) {
+ super(String.format("Invalid transaction handle: %d", transactionHandle));
+ this.transactionHandle = transactionHandle;
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/mapper/FirebirdDialectExceptionMapper.java b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/mapper/FirebirdDialectExceptionMapper.java
index fbfe347cf05..dd640120296 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/mapper/FirebirdDialectExceptionMapper.java
+++ b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/mapper/FirebirdDialectExceptionMapper.java
@@ -19,8 +19,22 @@ package org.apache.shardingsphere.database.exception.firebird.mapper;
import org.apache.shardingsphere.database.exception.core.exception.SQLDialectException;
import org.apache.shardingsphere.database.exception.core.exception.connection.AccessDeniedException;
+import org.apache.shardingsphere.database.exception.core.exception.data.InvalidParameterValueException;
+import org.apache.shardingsphere.database.exception.core.exception.syntax.database.DatabaseCreateExistsException;
+import org.apache.shardingsphere.database.exception.core.exception.syntax.database.DatabaseDropNotExistsException;
import org.apache.shardingsphere.database.exception.core.exception.syntax.database.UnknownDatabaseException;
+import org.apache.shardingsphere.database.exception.core.exception.syntax.sql.DialectSQLParsingException;
+import org.apache.shardingsphere.database.exception.core.exception.syntax.table.TableExistsException;
import org.apache.shardingsphere.database.exception.core.mapper.SQLDialectExceptionMapper;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.BatchAlreadyOpenedException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.BatchParametersRequiredException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.BatchTooBigException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.ExcessTransactionsException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchHandleException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchMessageFormatException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchParameterVersionException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidStatementHandleException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidTransactionHandleException;
import org.apache.shardingsphere.database.exception.firebird.vendor.FirebirdVendorError;
import org.apache.shardingsphere.infra.exception.external.sql.vendor.VendorError;
import org.apache.shardingsphere.infra.exception.generic.UnknownSQLException;
@@ -37,9 +51,53 @@ public final class FirebirdDialectExceptionMapper implements SQLDialectException
if (sqlDialectException instanceof UnknownDatabaseException) {
return toSQLException(FirebirdVendorError.UNAVAILABLE_DATABASE, ((UnknownDatabaseException) sqlDialectException).getDatabaseName());
}
+ if (sqlDialectException instanceof DatabaseCreateExistsException) {
+ return toSQLException(FirebirdVendorError.DATABASE_ALREADY_EXISTS, ((DatabaseCreateExistsException) sqlDialectException).getDatabaseName());
+ }
+ if (sqlDialectException instanceof DatabaseDropNotExistsException) {
+ return toSQLException(FirebirdVendorError.UNAVAILABLE_DATABASE, ((DatabaseDropNotExistsException) sqlDialectException).getDatabaseName());
+ }
if (sqlDialectException instanceof AccessDeniedException) {
return toSQLException(FirebirdVendorError.LOGIN_FAILED);
}
+ if (sqlDialectException instanceof InvalidBatchHandleException) {
+ return toSQLException(FirebirdVendorError.INVALID_BATCH_HANDLE);
+ }
+ if (sqlDialectException instanceof BatchTooBigException) {
+ return toSQLException(FirebirdVendorError.BATCH_TOO_BIG);
+ }
+ if (sqlDialectException instanceof BatchAlreadyOpenedException) {
+ return toSQLException(FirebirdVendorError.BATCH_ALREADY_OPENED);
+ }
+ if (sqlDialectException instanceof InvalidBatchParameterVersionException) {
+ InvalidBatchParameterVersionException ex = (InvalidBatchParameterVersionException) sqlDialectException;
+ return toSQLException(FirebirdVendorError.INVALID_BATCH_PARAMETER_VERSION, ex.getVersion(), ex.getExpectedVersion());
+ }
+ if (sqlDialectException instanceof BatchParametersRequiredException) {
+ return toSQLException(FirebirdVendorError.BATCH_PARAMETERS_REQUIRED);
+ }
+ if (sqlDialectException instanceof InvalidBatchMessageFormatException) {
+ return toSQLException(FirebirdVendorError.SQLDA_ERROR);
+ }
+ if (sqlDialectException instanceof InvalidStatementHandleException) {
+ return toSQLException(FirebirdVendorError.INVALID_STATEMENT_HANDLE);
+ }
+ if (sqlDialectException instanceof InvalidTransactionHandleException) {
+ return toSQLException(FirebirdVendorError.INVALID_TRANSACTION_HANDLE);
+ }
+ if (sqlDialectException instanceof ExcessTransactionsException) {
+ return toSQLException(FirebirdVendorError.EXCESS_TRANSACTIONS, ((ExcessTransactionsException) sqlDialectException).getMaxTransactions());
+ }
+ if (sqlDialectException instanceof DialectSQLParsingException) {
+ DialectSQLParsingException ex = (DialectSQLParsingException) sqlDialectException;
+ return toSQLException(FirebirdVendorError.DYNAMIC_SQL_ERROR, ex.getMessage());
+ }
+ if (sqlDialectException instanceof TableExistsException) {
+ return toSQLException(FirebirdVendorError.TABLE_ALREADY_EXISTS, ((TableExistsException) sqlDialectException).getTableName());
+ }
+ if (sqlDialectException instanceof InvalidParameterValueException) {
+ return toSQLException(FirebirdVendorError.CHARSET_NOT_FOUND, ((InvalidParameterValueException) sqlDialectException).getParameterValue());
+ }
return new UnknownSQLException(sqlDialectException).toSQLException();
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
index f00a726c59e..891cde102b5 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
@@ -30,7 +30,23 @@ import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
@Getter
public enum FirebirdState implements SQLState {
- UNAVAILABLE_DATABASE("08001");
+ UNAVAILABLE_DATABASE("08001"),
+
+ INVALID_BATCH_HANDLE("08003"),
+
+ BATCH_TOO_BIG("54000"),
+
+ CHARSET_NOT_FOUND("2C000"),
+
+ INVALID_STATEMENT_HANDLE("26000"),
+
+ INVALID_TRANSACTION_HANDLE("08003"),
+
+ BATCH_ALREADY_OPENED("08002"),
+
+ BATCH_PARAMETERS_REQUIRED("07001"),
+
+ SQLDA_ERROR("07002");
private final String value;
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/vendor/FirebirdVendorError.java b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/vendor/FirebirdVendorError.java
index 2c5280ee850..e05a78869ea 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/vendor/FirebirdVendorError.java
+++ b/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/vendor/FirebirdVendorError.java
@@ -28,12 +28,9 @@ import org.firebirdsql.gds.ISCConstants;
/**
* Firebird vendor error.
*
- * <p>The vendor code carries the native Firebird GDSCODE, which the client (Jaybird) uses to look up both the message
- * template and the SQL state from its own catalog. The status vector transmits the GDSCODE plus a single string
- * argument, so the reason here holds only that argument. With Jaybird's current message rendering a {@code {0}}
- * placeholder is filled by the argument and a template without placeholders is followed by the argument; an empty reason
- * sends no argument, leaving the catalog message as-is. GDSCODEs whose templates require multiple arguments are avoided
- * until the status vector can carry them.</p>
+ * <p>Kernel {@code ShardingSphereSQLException} types, such as metadata column-not-found errors from the binder, are
+ * converted before the Firebird SQL dialect mapper is reached. They need a separate Firebird remapping path instead of
+ * enum entries here.</p>
*
* @see <a href="https://www.firebirdsql.org/file/documentation/chunk/en/refdocs/fblangref40/fblangref40-appx02-sqlcodes.html">SQLCODE and GDSCODE Error Codes</a>
*/
@@ -43,7 +40,33 @@ public enum FirebirdVendorError implements VendorError {
UNAVAILABLE_DATABASE(FirebirdState.UNAVAILABLE_DATABASE, ISCConstants.isc_unavailable, "%s"),
- LOGIN_FAILED(XOpenSQLState.INVALID_AUTHORIZATION_SPECIFICATION, ISCConstants.isc_login, "");
+ DATABASE_ALREADY_EXISTS(XOpenSQLState.GENERAL_ERROR, ISCConstants.isc_db_or_file_exists, "%s"),
+
+ INVALID_BATCH_HANDLE(FirebirdState.INVALID_BATCH_HANDLE, ISCConstants.isc_bad_batch_handle, ""),
+
+ BATCH_TOO_BIG(FirebirdState.BATCH_TOO_BIG, ISCConstants.isc_batch_too_big, ""),
+
+ DYNAMIC_SQL_ERROR(XOpenSQLState.SYNTAX_ERROR, ISCConstants.isc_dsql_error, "%s"),
+
+ TABLE_ALREADY_EXISTS(XOpenSQLState.DUPLICATE, ISCConstants.isc_dyn_dup_table, "%s"),
+
+ LOGIN_FAILED(XOpenSQLState.INVALID_AUTHORIZATION_SPECIFICATION, ISCConstants.isc_login, ""),
+
+ CHARSET_NOT_FOUND(FirebirdState.CHARSET_NOT_FOUND, ISCConstants.isc_charset_not_found, "CHARACTER SET %s is not defined"),
+
+ INVALID_STATEMENT_HANDLE(FirebirdState.INVALID_STATEMENT_HANDLE, ISCConstants.isc_bad_stmt_handle, ""),
+
+ INVALID_TRANSACTION_HANDLE(FirebirdState.INVALID_TRANSACTION_HANDLE, ISCConstants.isc_bad_trans_handle, ""),
+
+ EXCESS_TRANSACTIONS(XOpenSQLState.GENERAL_ERROR, ISCConstants.isc_excess_trans, "attempt to start more than %d transactions"),
+
+ BATCH_ALREADY_OPENED(FirebirdState.BATCH_ALREADY_OPENED, ISCConstants.isc_batch_open, ""),
+
+ INVALID_BATCH_PARAMETER_VERSION(XOpenSQLState.DATA_EXCEPTION, ISCConstants.isc_batch_param_version, "Wrong version of batch parameters block %d, should be %d"),
+
+ BATCH_PARAMETERS_REQUIRED(FirebirdState.BATCH_PARAMETERS_REQUIRED, ISCConstants.isc_batch_param, "Statement used in batch must have parameters"),
+
+ SQLDA_ERROR(FirebirdState.SQLDA_ERROR, ISCConstants.isc_dsql_sqlda_err, "");
private final SQLState sqlState;
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchAlreadyOpenedExceptionTest.java
similarity index 60%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchAlreadyOpenedExceptionTest.java
index f00a726c59e..e4119ec7f36 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchAlreadyOpenedExceptionTest.java
@@ -15,22 +15,19 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.junit.jupiter.api.Test;
-/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
- */
-@RequiredArgsConstructor
-@Getter
-public enum FirebirdState implements SQLState {
-
- UNAVAILABLE_DATABASE("08001");
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class BatchAlreadyOpenedExceptionTest {
- private final String value;
+ @Test
+ void assertGetMessage() {
+ BatchAlreadyOpenedException actual = new BatchAlreadyOpenedException(42);
+ assertThat(actual.getMessage(), is("Batch already opened for statement handle 42"));
+ assertThat(actual.getStatementHandle(), is(42));
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchParametersRequiredExceptionTest.java
similarity index 59%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchParametersRequiredExceptionTest.java
index f00a726c59e..cdff56a0fcf 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchParametersRequiredExceptionTest.java
@@ -15,22 +15,19 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.junit.jupiter.api.Test;
-/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
- */
-@RequiredArgsConstructor
-@Getter
-public enum FirebirdState implements SQLState {
-
- UNAVAILABLE_DATABASE("08001");
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class BatchParametersRequiredExceptionTest {
- private final String value;
+ @Test
+ void assertGetMessage() {
+ BatchParametersRequiredException actual = new BatchParametersRequiredException(42);
+ assertThat(actual.getMessage(), is("Statement used in batch must have parameters"));
+ assertThat(actual.getStatementHandle(), is(42));
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchTooBigExceptionTest.java
similarity index 58%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchTooBigExceptionTest.java
index f00a726c59e..56e6e7bd126 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/BatchTooBigExceptionTest.java
@@ -15,22 +15,19 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.junit.jupiter.api.Test;
-/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
- */
-@RequiredArgsConstructor
-@Getter
-public enum FirebirdState implements SQLState {
-
- UNAVAILABLE_DATABASE("08001");
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class BatchTooBigExceptionTest {
- private final String value;
+ @Test
+ void assertGetMessage() {
+ BatchTooBigException actual = new BatchTooBigException(42, 1L, 8L, 8L);
+ assertThat(actual.getMessage(), is("Batch is too big: accumulated 1 + incoming 8 data bytes exceeds buffer size limit 8 bytes"));
+ assertThat(actual.getStatementHandle(), is(42));
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/ExcessTransactionsExceptionTest.java
similarity index 60%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/ExcessTransactionsExceptionTest.java
index f00a726c59e..e2928758859 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/ExcessTransactionsExceptionTest.java
@@ -15,22 +15,19 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.junit.jupiter.api.Test;
-/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
- */
-@RequiredArgsConstructor
-@Getter
-public enum FirebirdState implements SQLState {
-
- UNAVAILABLE_DATABASE("08001");
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class ExcessTransactionsExceptionTest {
- private final String value;
+ @Test
+ void assertGetMessage() {
+ ExcessTransactionsException actual = new ExcessTransactionsException(1);
+ assertThat(actual.getMessage(), is("Attempt to start more than 1 transactions"));
+ assertThat(actual.getMaxTransactions(), is(1));
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchHandleExceptionTest.java
similarity index 60%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchHandleExceptionTest.java
index f00a726c59e..595df82aa26 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchHandleExceptionTest.java
@@ -15,22 +15,19 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.junit.jupiter.api.Test;
-/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
- */
-@RequiredArgsConstructor
-@Getter
-public enum FirebirdState implements SQLState {
-
- UNAVAILABLE_DATABASE("08001");
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class InvalidBatchHandleExceptionTest {
- private final String value;
+ @Test
+ void assertGetMessage() {
+ InvalidBatchHandleException actual = new InvalidBatchHandleException(42);
+ assertThat(actual.getMessage(), is("Invalid batch handle: statement handle 42"));
+ assertThat(actual.getStatementHandle(), is(42));
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchMessageFormatExceptionTest.java
similarity index 57%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchMessageFormatExceptionTest.java
index f00a726c59e..aecef383d8c 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchMessageFormatExceptionTest.java
@@ -15,22 +15,19 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.junit.jupiter.api.Test;
-/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
- */
-@RequiredArgsConstructor
-@Getter
-public enum FirebirdState implements SQLState {
-
- UNAVAILABLE_DATABASE("08001");
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class InvalidBatchMessageFormatExceptionTest {
- private final String value;
+ @Test
+ void assertGetMessage() {
+ InvalidBatchMessageFormatException actual = new InvalidBatchMessageFormatException("invalid message length");
+ assertThat(actual.getMessage(), is("Invalid batch message format: invalid message length"));
+ assertThat(actual.getDetail(), is("invalid message length"));
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchParameterVersionExceptionTest.java
similarity index 56%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchParameterVersionExceptionTest.java
index f00a726c59e..aaac564267c 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidBatchParameterVersionExceptionTest.java
@@ -15,22 +15,20 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.junit.jupiter.api.Test;
-/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
- */
-@RequiredArgsConstructor
-@Getter
-public enum FirebirdState implements SQLState {
-
- UNAVAILABLE_DATABASE("08001");
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class InvalidBatchParameterVersionExceptionTest {
- private final String value;
+ @Test
+ void assertGetMessage() {
+ InvalidBatchParameterVersionException actual = new InvalidBatchParameterVersionException(2, 1);
+ assertThat(actual.getMessage(), is("Wrong version of batch parameters block 2, should be 1"));
+ assertThat(actual.getVersion(), is(2));
+ assertThat(actual.getExpectedVersion(), is(1));
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidStatementHandleExceptionTest.java
similarity index 60%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidStatementHandleExceptionTest.java
index f00a726c59e..ef7c671ecb2 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidStatementHandleExceptionTest.java
@@ -15,22 +15,19 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.junit.jupiter.api.Test;
-/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
- */
-@RequiredArgsConstructor
-@Getter
-public enum FirebirdState implements SQLState {
-
- UNAVAILABLE_DATABASE("08001");
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class InvalidStatementHandleExceptionTest {
- private final String value;
+ @Test
+ void assertGetMessage() {
+ InvalidStatementHandleException actual = new InvalidStatementHandleException(42);
+ assertThat(actual.getMessage(), is("Invalid statement handle: 42"));
+ assertThat(actual.getStatementHandle(), is(42));
+ }
}
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidTransactionHandleExceptionTest.java
similarity index 60%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidTransactionHandleExceptionTest.java
index f00a726c59e..3e88127acdb 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/exception/protocol/InvalidTransactionHandleExceptionTest.java
@@ -15,22 +15,19 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.database.exception.firebird.exception.protocol;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+import org.junit.jupiter.api.Test;
-/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
- */
-@RequiredArgsConstructor
-@Getter
-public enum FirebirdState implements SQLState {
-
- UNAVAILABLE_DATABASE("08001");
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+class InvalidTransactionHandleExceptionTest {
- private final String value;
+ @Test
+ void assertGetMessage() {
+ InvalidTransactionHandleException actual = new InvalidTransactionHandleException(42);
+ assertThat(actual.getMessage(), is("Invalid transaction handle: 42"));
+ assertThat(actual.getTransactionHandle(), is(42));
+ }
}
diff --git a/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/mapper/FirebirdDialectExceptionMapperTest.java b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/mapper/FirebirdDialectExceptionMapperTest.java
index 7593ebbd061..0cf97cf9d42 100644
--- a/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/mapper/FirebirdDialectExceptionMapperTest.java
+++ b/database/exception/dialect/firebird/src/test/java/org/apache/shardingsphere/database/exception/firebird/mapper/FirebirdDialectExceptionMapperTest.java
@@ -21,8 +21,22 @@ import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoa
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.database.exception.core.exception.SQLDialectException;
import org.apache.shardingsphere.database.exception.core.exception.connection.AccessDeniedException;
+import org.apache.shardingsphere.database.exception.core.exception.data.InvalidParameterValueException;
+import org.apache.shardingsphere.database.exception.core.exception.syntax.database.DatabaseCreateExistsException;
+import org.apache.shardingsphere.database.exception.core.exception.syntax.database.DatabaseDropNotExistsException;
import org.apache.shardingsphere.database.exception.core.exception.syntax.database.UnknownDatabaseException;
+import org.apache.shardingsphere.database.exception.core.exception.syntax.sql.DialectSQLParsingException;
+import org.apache.shardingsphere.database.exception.core.exception.syntax.table.TableExistsException;
import org.apache.shardingsphere.database.exception.core.mapper.SQLDialectExceptionMapper;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.BatchAlreadyOpenedException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.BatchParametersRequiredException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.BatchTooBigException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.ExcessTransactionsException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchHandleException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchMessageFormatException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchParameterVersionException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidStatementHandleException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidTransactionHandleException;
import org.apache.shardingsphere.database.exception.firebird.vendor.FirebirdVendorError;
import org.apache.shardingsphere.infra.exception.external.sql.vendor.VendorError;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
@@ -45,11 +59,82 @@ class FirebirdDialectExceptionMapperTest {
assertSQLException(mapper.convert(new UnknownDatabaseException("logic_db")), FirebirdVendorError.UNAVAILABLE_DATABASE, "logic_db");
}
+ @Test
+ void assertConvertWithDatabaseCreateExists() {
+ assertSQLException(mapper.convert(new DatabaseCreateExistsException("logic_db")), FirebirdVendorError.DATABASE_ALREADY_EXISTS, "logic_db");
+ }
+
+ @Test
+ void assertConvertWithDatabaseDropNotExists() {
+ assertSQLException(mapper.convert(new DatabaseDropNotExistsException("logic_db")), FirebirdVendorError.UNAVAILABLE_DATABASE, "logic_db");
+ }
+
@Test
void assertConvertWithAccessDenied() {
assertSQLException(mapper.convert(new AccessDeniedException("root", "127.0.0.1", true)), FirebirdVendorError.LOGIN_FAILED);
}
+ @Test
+ void assertConvertWithBatchTooBig() {
+ assertSQLException(mapper.convert(new BatchTooBigException(42, 1L, 8L, 8L)), FirebirdVendorError.BATCH_TOO_BIG);
+ }
+
+ @Test
+ void assertConvertWithTableExists() {
+ assertSQLException(mapper.convert(new TableExistsException("t_order")), FirebirdVendorError.TABLE_ALREADY_EXISTS, "t_order");
+ }
+
+ @Test
+ void assertConvertWithDialectSQLParsing() {
+ assertSQLException(mapper.convert(new DialectSQLParsingException("You have an error in your SQL syntax", "SELEC", 1)), FirebirdVendorError.DYNAMIC_SQL_ERROR,
+ "You have an error in your SQL syntax");
+ }
+
+ @Test
+ void assertConvertWithInvalidBatchHandle() {
+ assertSQLException(mapper.convert(new InvalidBatchHandleException(42)), FirebirdVendorError.INVALID_BATCH_HANDLE);
+ }
+
+ @Test
+ void assertConvertWithBatchAlreadyOpened() {
+ assertSQLException(mapper.convert(new BatchAlreadyOpenedException(42)), FirebirdVendorError.BATCH_ALREADY_OPENED);
+ }
+
+ @Test
+ void assertConvertWithInvalidBatchParameterVersion() {
+ assertSQLException(mapper.convert(new InvalidBatchParameterVersionException(2, 1)), FirebirdVendorError.INVALID_BATCH_PARAMETER_VERSION, 2, 1);
+ }
+
+ @Test
+ void assertConvertWithBatchParametersRequired() {
+ assertSQLException(mapper.convert(new BatchParametersRequiredException(42)), FirebirdVendorError.BATCH_PARAMETERS_REQUIRED);
+ }
+
+ @Test
+ void assertConvertWithInvalidBatchMessageFormat() {
+ assertSQLException(mapper.convert(new InvalidBatchMessageFormatException("invalid message length")), FirebirdVendorError.SQLDA_ERROR);
+ }
+
+ @Test
+ void assertConvertWithInvalidStatementHandle() {
+ assertSQLException(mapper.convert(new InvalidStatementHandleException(42)), FirebirdVendorError.INVALID_STATEMENT_HANDLE);
+ }
+
+ @Test
+ void assertConvertWithInvalidTransactionHandle() {
+ assertSQLException(mapper.convert(new InvalidTransactionHandleException(42)), FirebirdVendorError.INVALID_TRANSACTION_HANDLE);
+ }
+
+ @Test
+ void assertConvertWithExcessTransactions() {
+ assertSQLException(mapper.convert(new ExcessTransactionsException(1)), FirebirdVendorError.EXCESS_TRANSACTIONS, 1);
+ }
+
+ @Test
+ void assertConvertWithInvalidParameterValue() {
+ assertSQLException(mapper.convert(new InvalidParameterValueException("names", "foo_charset")), FirebirdVendorError.CHARSET_NOT_FOUND, "foo_charset");
+ }
+
@Test
void assertConvertWithUnknownException() {
SQLException actual = mapper.convert(mock(SQLDialectException.class));
diff --git a/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/codec/FirebirdPacketCodecEngine.java b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/codec/FirebirdPacketCodecEngine.java
index bb30837ec7c..911422f4a59 100644
--- a/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/codec/FirebirdPacketCodecEngine.java
+++ b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/codec/FirebirdPacketCodecEngine.java
@@ -19,10 +19,12 @@ package org.apache.shardingsphere.database.protocol.firebird.codec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
+import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import org.apache.shardingsphere.database.protocol.codec.DatabasePacketCodecEngine;
import org.apache.shardingsphere.database.protocol.constant.CommonConstants;
import org.apache.shardingsphere.database.protocol.firebird.constant.FirebirdConstant;
+import org.apache.shardingsphere.database.protocol.firebird.err.FirebirdErrorPacketFactory;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.FirebirdCommandPacketFactory;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.FirebirdCommandPacketType;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchColumnDescriptor;
@@ -78,10 +80,24 @@ public final class FirebirdPacketCodecEngine implements DatabasePacketCodecEngin
// CHECKSTYLE:OFF
} catch (final RuntimeException ex) {
// CHECKSTYLE:ON
- // TODO replace with a proper Firebird error response packet; for now close the channel so the client fails fast instead of hanging.
- context.close();
- throw ex;
+ for (Object each : out) {
+ if (each instanceof ByteBuf) {
+ ((ByteBuf) each).release();
+ }
+ }
+ out.clear();
+ resetState();
+ in.skipBytes(in.readableBytes());
+ context.channel().writeAndFlush(FirebirdErrorPacketFactory.newInstance(ex)).addListener(ChannelFutureListener.CLOSE);
+ }
+ }
+
+ private void resetState() {
+ for (ByteBuf each : pendingMessages) {
+ each.release();
}
+ pendingMessages.clear();
+ pendingPacketType = null;
}
private void handleMultiPacket(final ChannelHandlerContext context, final ByteBuf in, final List<Object> out, final int firstPacketLength) {
@@ -159,7 +175,7 @@ public final class FirebirdPacketCodecEngine implements DatabasePacketCodecEngin
if (expectedLength < 0) {
return -1;
}
- return 0 == expectedLength ? readableBytes : (readableBytes >= expectedLength ? expectedLength : -1);
+ return 0 == expectedLength ? readableBytes : readableBytes >= expectedLength ? expectedLength : -1;
} catch (final IndexOutOfBoundsException ex) {
return -1;
} finally {
@@ -193,9 +209,7 @@ public final class FirebirdPacketCodecEngine implements DatabasePacketCodecEngin
} catch (final RuntimeException ex) {
// CHECKSTYLE:ON
payload.getByteBuf().resetWriterIndex();
- // TODO replace with a proper Firebird error response packet; for now close the channel so the client fails fast instead of hanging.
- context.close();
- throw ex;
+ FirebirdErrorPacketFactory.newInstance(ex).write(payload);
}
}
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/err/FirebirdErrorPacketFactory.java b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdErrorPacketFactory.java
similarity index 96%
rename from proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/err/FirebirdErrorPacketFactory.java
rename to database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdErrorPacketFactory.java
index 3506bc4c1fb..a0cb11eb17b 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/err/FirebirdErrorPacketFactory.java
+++ b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdErrorPacketFactory.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.proxy.frontend.firebird.err;
+package org.apache.shardingsphere.database.protocol.firebird.err;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
diff --git a/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdErrorSegmentResolver.java b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdErrorSegmentResolver.java
new file mode 100644
index 00000000000..48955971b9d
--- /dev/null
+++ b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdErrorSegmentResolver.java
@@ -0,0 +1,122 @@
+/*
+ * 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.firebird.err;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.apache.shardingsphere.database.protocol.firebird.err.FirebirdStatusVector.Segment;
+import org.firebirdsql.gds.GDSExceptionHelper;
+import org.firebirdsql.gds.GDSExceptionHelper.GDSMessage;
+import org.firebirdsql.gds.ISCConstants;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Resolver of Firebird status vector segments that preserves the native gdscode.
+ *
+ * <p>The backend driver (Jaybird) flattens the status vector into a single message and discards the structured
+ * arguments, so to keep the honest gdscode this resolver reverses Jaybird's rendering: it recovers the template
+ * arguments from the live Jaybird catalog (verified by re-render) and keeps the native gdscode, falling back to a
+ * verbatim {@code isc_random} segment when recovery is not reliable. Used by {@link FirebirdStatusVector}.</p>
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class FirebirdErrorSegmentResolver {
+
+ private static final String SEGMENT_SEPARATOR = "; ";
+
+ private static final String PARAMETER_MARKER = "" + (char) 1 + (char) 2;
+
+ /**
+ * Resolve status vector segments keeping the native gdscode.
+ *
+ * @param errorCode native error code reported by the backend driver
+ * @param message error message stripped of the trailing SQLState/ISC code suffix
+ * @param sqlState SQL state
+ * @return resolved segments
+ */
+ public static List<Segment> resolve(final int errorCode, final String message, final String sqlState) {
+ int code = errorCode >= ISCConstants.isc_arith_except ? errorCode : ISCConstants.isc_random;
+ return buildSegments(code, message, sqlState);
+ }
+
+ private static List<Segment> buildSegments(final int code, final String message, final String sqlState) {
+ List<String> parameters = recoverParameters(code, message);
+ if (null != parameters) {
+ return buildTemplatedSegments(code, parameters, message);
+ }
+ if (GDSExceptionHelper.getMessage(code).getParamCount() >= 2) {
+ return Collections.singletonList(new Segment(ISCConstants.isc_random, toArguments(message), blankToNull(sqlState)));
+ }
+ return Collections.singletonList(new Segment(code, toArguments(message), null));
+ }
+
+ private static List<Segment> buildTemplatedSegments(final int code, final List<String> parameters, final String message) {
+ List<Segment> result = new ArrayList<>();
+ result.add(new Segment(code, parameters, null));
+ String remainder = stripSeparator(message.substring(render(code, parameters).length()));
+ if (!remainder.isEmpty()) {
+ result.add(new Segment(ISCConstants.isc_random, Collections.singletonList(remainder), null));
+ }
+ return result;
+ }
+
+ private static List<String> recoverParameters(final int code, final String message) {
+ GDSMessage template = GDSExceptionHelper.getMessage(code);
+ int count = template.getParamCount();
+ for (int i = 0; i < count; i++) {
+ template.setParameter(i, PARAMETER_MARKER);
+ }
+ String[] literals = template.toString().split(Pattern.quote(PARAMETER_MARKER), -1);
+ if (literals.length != count + 1 || !message.startsWith(literals[0])) {
+ return null;
+ }
+ List<String> result = new ArrayList<>(count);
+ int position = literals[0].length();
+ for (int i = 1; i <= count; i++) {
+ String tail = literals[i];
+ int end = tail.isEmpty() ? message.length() : message.indexOf(tail, position);
+ if (end < 0) {
+ return null;
+ }
+ result.add(message.substring(position, end));
+ position = end + tail.length();
+ }
+ return message.startsWith(render(code, result)) ? result : null;
+ }
+
+ private static String render(final int code, final List<String> parameters) {
+ GDSMessage template = GDSExceptionHelper.getMessage(code);
+ template.setParameters(parameters);
+ return template.toString();
+ }
+
+ private static List<String> toArguments(final String message) {
+ return message.isEmpty() ? Collections.emptyList() : Collections.singletonList(message);
+ }
+
+ private static String stripSeparator(final String remainder) {
+ return remainder.startsWith(SEGMENT_SEPARATOR) ? remainder.substring(SEGMENT_SEPARATOR.length()) : remainder;
+ }
+
+ private static String blankToNull(final String sqlState) {
+ return null == sqlState || sqlState.isEmpty() ? null : sqlState;
+ }
+}
diff --git a/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdStatusVector.java b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdStatusVector.java
index a443e201fa3..6a3269f2ef5 100644
--- a/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdStatusVector.java
+++ b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdStatusVector.java
@@ -18,11 +18,13 @@
package org.apache.shardingsphere.database.protocol.firebird.err;
import lombok.Getter;
+import lombok.RequiredArgsConstructor;
import org.apache.shardingsphere.database.protocol.firebird.packet.FirebirdPacket;
import org.apache.shardingsphere.database.protocol.firebird.payload.FirebirdPacketPayload;
import org.firebirdsql.gds.ISCConstants;
import java.sql.SQLException;
+import java.util.List;
/**
* Firebird status vector for errors handling.
@@ -30,30 +32,53 @@ import java.sql.SQLException;
@Getter
public final class FirebirdStatusVector extends FirebirdPacket {
+ private static final String STATE_SUFFIX = " [SQLState:";
+
private final int gdsCode;
private final String errorMessage;
+ private final List<Segment> segments;
+
public FirebirdStatusVector(final SQLException ex) {
- gdsCode = ex.getErrorCode() >= ISCConstants.isc_arith_except ? ex.getErrorCode() : ISCConstants.isc_random;
- String rawMessage = null == ex.getMessage() ? "" : ex.getMessage();
- int idx = rawMessage.indexOf(';');
- String message = idx >= 0 ? rawMessage.substring(idx + 1).trim() : rawMessage;
- int stateIdx = message.indexOf(" [SQLState:");
- if (stateIdx >= 0) {
- message = message.substring(0, stateIdx).trim();
- }
- errorMessage = message;
+ errorMessage = stripStateSuffix(null == ex.getMessage() ? "" : ex.getMessage());
+ segments = FirebirdErrorSegmentResolver.resolve(ex.getErrorCode(), errorMessage, ex.getSQLState());
+ gdsCode = segments.get(0).gdsCode;
+ }
+
+ private static String stripStateSuffix(final String message) {
+ int index = message.indexOf(STATE_SUFFIX);
+ return (index >= 0 ? message.substring(0, index) : message).trim();
}
@Override
protected void write(final FirebirdPacketPayload payload) {
- payload.writeInt4(ISCConstants.isc_arg_gds);
- payload.writeInt4(gdsCode);
- if (!errorMessage.isEmpty()) {
- payload.writeInt4(ISCConstants.isc_arg_string);
- payload.writeString(errorMessage);
+ for (Segment each : segments) {
+ payload.writeInt4(ISCConstants.isc_arg_gds);
+ payload.writeInt4(each.gdsCode);
+ for (String argument : each.arguments) {
+ payload.writeInt4(ISCConstants.isc_arg_string);
+ payload.writeString(argument);
+ }
+ if (null != each.sqlState) {
+ payload.writeInt4(ISCConstants.isc_arg_sql_state);
+ payload.writeString(each.sqlState);
+ }
}
payload.writeInt4(ISCConstants.isc_arg_end);
}
+
+ /**
+ * Single segment of the status vector.
+ */
+ @RequiredArgsConstructor
+ @Getter
+ public static final class Segment {
+
+ private final int gdsCode;
+
+ private final List<String> arguments;
+
+ private final String sqlState;
+ }
}
diff --git a/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchMessageCommandPacket.java b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchMessageCommandPacket.java
index c04a5ba0b26..9252954c002 100644
--- a/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchMessageCommandPacket.java
+++ b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchMessageCommandPacket.java
@@ -19,7 +19,7 @@ package org.apache.shardingsphere.database.protocol.firebird.packet.command.quer
import io.netty.buffer.ByteBuf;
import lombok.Getter;
-import org.apache.shardingsphere.database.protocol.firebird.exception.FirebirdProtocolException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchHandleException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.FirebirdCommandPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.FirebirdBinaryColumnType;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.statement.execute.protocol.FirebirdBinaryProtocolValue;
@@ -64,7 +64,6 @@ public final class FirebirdBatchMessageCommandPacket extends FirebirdCommandPack
* @param payload Firebird packet payload
* @param connectionId connection ID
* @return length of packet
- * @throws FirebirdProtocolException firebird protocol exception
*/
public static int getLength(final FirebirdPacketPayload payload, final int connectionId) {
return getLength(payload, connectionId, payload.getByteBuf().readerIndex(), payload.getByteBuf().readableBytes());
@@ -94,7 +93,7 @@ public final class FirebirdBatchMessageCommandPacket extends FirebirdCommandPack
int statementHandle = payload.readInt4();
FirebirdBatchStatement batchStatement = FirebirdBatchRegistry.getInstance().getBatchStatement(connectionId, statementHandle);
if (null == batchStatement) {
- throw new FirebirdProtocolException("Batch statement not found for connectionId: " + connectionId + ", statement handle: " + statementHandle);
+ throw new InvalidBatchHandleException(statementHandle);
}
return parseBatchMessages(payload, startReaderIndex, payload.readInt4Unsigned(), batchStatement.getColumnDescriptors());
}
diff --git a/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchStatement.java b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchStatement.java
index e9f1b96033e..5ad4984a273 100644
--- a/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchStatement.java
+++ b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchStatement.java
@@ -38,16 +38,18 @@ public final class FirebirdBatchStatement {
private final boolean recordCounts;
+ private final boolean multiError;
+
private final List<List<Object>> parameterValues = new ArrayList<>();
private long accumulatedSize;
public FirebirdBatchStatement(final int statementHandle) {
- this(statementHandle, Collections.emptyList(), 0L, false);
+ this(statementHandle, Collections.emptyList(), 0L, false, false);
}
public FirebirdBatchStatement(final int statementHandle, final List<FirebirdBatchColumnDescriptor> columnDescriptors, final long bufferSize) {
- this(statementHandle, columnDescriptors, bufferSize, false);
+ this(statementHandle, columnDescriptors, bufferSize, false, false);
}
/**
diff --git a/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/statement/execute/protocol/util/FirebirdDateTimeUtils.java b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/statement/execute/protocol/util/FirebirdDateTimeUtils.java
index fa849612114..371ebd02b6e 100644
--- a/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/statement/execute/protocol/util/FirebirdDateTimeUtils.java
+++ b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/statement/execute/protocol/util/FirebirdDateTimeUtils.java
@@ -61,7 +61,7 @@ public final class FirebirdDateTimeUtils {
hour = localDateTime.getHour();
minute = localDateTime.getMinute();
second = localDateTime.getSecond();
- fractions = (localDateTime.getNano() / NANOSECONDS_PER_FRACTION) % FRACTIONS_PER_SECOND;
+ fractions = localDateTime.getNano() / NANOSECONDS_PER_FRACTION % FRACTIONS_PER_SECOND;
}
public FirebirdDateTimeUtils() {
@@ -104,8 +104,8 @@ public final class FirebirdDateTimeUtils {
}
private int convertDate(final int c, final int ya, final int cpMonth) {
- return (146097 * c) / 4
- + (1461 * ya) / 4
+ return 146097 * c / 4
+ + 1461 * ya / 4
+ (153 * cpMonth + 2) / 5
+ day + 1721119 - 2400001;
}
diff --git a/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/generic/FirebirdBatchCompletionStateResponse.java b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/generic/FirebirdBatchCompletionStateResponse.java
index bcb85e3fe1e..1f0db1f052b 100644
--- a/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/generic/FirebirdBatchCompletionStateResponse.java
+++ b/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/generic/FirebirdBatchCompletionStateResponse.java
@@ -17,22 +17,33 @@
package org.apache.shardingsphere.database.protocol.firebird.packet.generic;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.database.protocol.firebird.err.FirebirdStatusVector;
import org.apache.shardingsphere.database.protocol.firebird.packet.FirebirdPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.FirebirdCommandPacketType;
import org.apache.shardingsphere.database.protocol.firebird.payload.FirebirdPacketPayload;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Batch completion state response (op_batch_cs) for Firebird.
+ *
+ * <p>Failures are reported as detailed errors (record number plus status vector). The simplified-errors list
+ * (p_batch_errors, record numbers without a status vector) is always empty: the proxy holds the full status vector for
+ * the failure, so it never needs the record-number-only fallback that Firebird uses to cap server-side memory.</p>
+ */
public class FirebirdBatchCompletionStateResponse extends FirebirdPacket {
private int statementHandle;
private long recordsCount;
- private long batchVectors;
-
- private long batchErrors;
-
private int[] updateCounts = new int[0];
+ private final List<DetailedError> detailedErrors = new ArrayList<>();
+
/**
* Set statement handle.
*
@@ -66,18 +77,44 @@ public class FirebirdBatchCompletionStateResponse extends FirebirdPacket {
return this;
}
+ /**
+ * Add detailed error for a failed batch message.
+ *
+ * @param element zero-based index of the failed batch message
+ * @param statusVector status vector describing the failure
+ * @return this response
+ */
+ public FirebirdBatchCompletionStateResponse addDetailedError(final int element, final FirebirdStatusVector statusVector) {
+ detailedErrors.add(new DetailedError(element, statusVector));
+ return this;
+ }
+
@Override
protected void write(final FirebirdPacketPayload payload) {
payload.writeInt4(FirebirdCommandPacketType.BATCH_CS.getValue());
payload.writeInt4(statementHandle);
payload.writeInt4(Math.toIntExact(recordsCount));
payload.writeInt4(updateCounts.length);
- batchVectors = 0;
- batchErrors = 0;
- payload.writeInt4((int) batchVectors);
- payload.writeInt4((int) batchErrors);
+ payload.writeInt4(detailedErrors.size());
+ payload.writeInt4(0);
for (int c : updateCounts) {
payload.writeInt4(c);
}
+ for (DetailedError each : detailedErrors) {
+ payload.writeInt4(each.element);
+ each.statusVector.write(payload);
+ }
+ }
+
+ /**
+ * Detailed error reporting a failed batch message.
+ */
+ @RequiredArgsConstructor
+ @Getter
+ public static final class DetailedError {
+
+ private final int element;
+
+ private final FirebirdStatusVector statusVector;
}
}
diff --git a/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/codec/FirebirdPacketCodecEngineTest.java b/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/codec/FirebirdPacketCodecEngineTest.java
index 7affea631f5..575d4b04fe6 100644
--- a/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/codec/FirebirdPacketCodecEngineTest.java
+++ b/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/codec/FirebirdPacketCodecEngineTest.java
@@ -19,19 +19,23 @@ package org.apache.shardingsphere.database.protocol.firebird.codec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.embedded.EmbeddedChannel;
import lombok.SneakyThrows;
+import org.apache.shardingsphere.database.protocol.codec.PacketCodec;
import org.apache.shardingsphere.database.protocol.constant.CommonConstants;
import org.apache.shardingsphere.database.protocol.firebird.constant.FirebirdConstant;
import org.apache.shardingsphere.database.protocol.firebird.constant.protocol.FirebirdProtocolVersion;
-import org.apache.shardingsphere.database.protocol.firebird.exception.FirebirdProtocolException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.FirebirdCommandPacketFactory;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.FirebirdCommandPacketType;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.FirebirdBinaryColumnType;
+import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchColumnDescriptor;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchRegistry;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchMessageCommandPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchStatement;
-import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchColumnDescriptor;
+import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket;
import org.apache.shardingsphere.database.protocol.firebird.payload.FirebirdPacketPayload;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
import org.firebirdsql.gds.BlrConstants;
@@ -42,6 +46,7 @@ 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.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.internal.configuration.plugins.Plugins;
@@ -57,8 +62,8 @@ import java.util.stream.Stream;
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.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.ArgumentMatchers.anyInt;
@@ -77,6 +82,8 @@ class FirebirdPacketCodecEngineTest {
private static final int BATCH_STATEMENT_HANDLE = 42;
+ private static final int UNKNOWN_COMMAND_TYPE = 99999;
+
private final FirebirdPacketCodecEngine codecEngine = new FirebirdPacketCodecEngine();
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -200,9 +207,15 @@ class FirebirdPacketCodecEngineTest {
@Test
void assertDecodeWithProcessPacketsExceptionAndNoPendingMessages() {
when(context.channel().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).get()).thenThrow(IllegalStateException.class);
+ ChannelFuture errorResponseFuture = mock(ChannelFuture.class);
+ when(context.channel().writeAndFlush(any())).thenReturn(errorResponseFuture);
ByteBuf in = createCommandPacket(FirebirdCommandPacketType.INFO_REQUEST, 8);
List<Object> out = new LinkedList<>();
- assertThrows(IllegalStateException.class, () -> codecEngine.decode(context, in, out));
+ codecEngine.decode(context, in, out);
+ assertTrue(out.isEmpty());
+ assertThat(in.readableBytes(), is(0));
+ assertThat(captureErrorResponse().getErrorCode(), is(335544382));
+ verify(errorResponseFuture).addListener(ChannelFutureListener.CLOSE);
}
@Test
@@ -212,7 +225,17 @@ class FirebirdPacketCodecEngineTest {
getPendingMessages().add(pendingMessage);
ByteBuf in = createCommandPacket(FirebirdCommandPacketType.INFO_REQUEST, 8);
List<Object> out = new LinkedList<>();
- assertThrows(IllegalStateException.class, () -> codecEngine.decode(context, in, out));
+ codecEngine.decode(context, in, out);
+ assertTrue(out.isEmpty());
+ assertTrue(getPendingMessages().isEmpty());
+ assertThat(pendingMessage.refCnt(), is(0));
+ assertThat(captureErrorResponse().getErrorCode(), is(335544382));
+ }
+
+ private FirebirdGenericResponsePacket captureErrorResponse() {
+ ArgumentCaptor<DatabasePacket> captor = ArgumentCaptor.forClass(DatabasePacket.class);
+ verify(context.channel()).writeAndFlush(captor.capture());
+ return (FirebirdGenericResponsePacket) captor.getValue();
}
@Test
@@ -224,13 +247,42 @@ class FirebirdPacketCodecEngineTest {
}
@Test
- void assertEncodeWithException() {
+ void assertEncodeWritesErrorResponseOnException() {
DatabasePacket packet = mock(DatabasePacket.class);
- ByteBuf byteBuf = mock(ByteBuf.class);
+ ByteBuf byteBuf = Unpooled.buffer();
doThrow(RuntimeException.class).when(packet).write(any(FirebirdPacketPayload.class));
- assertThrows(RuntimeException.class, () -> codecEngine.encode(context, packet, byteBuf));
- verify(byteBuf).resetWriterIndex();
- verify(context).close();
+ codecEngine.encode(context, packet, byteBuf);
+ assertThat(byteBuf.getInt(0), is(FirebirdCommandPacketType.RESPONSE.getValue()));
+ verify(context, never()).close();
+ }
+
+ @Test
+ void assertDecodeUnknownCommandWritesEncodedErrorAndClosesChannel() {
+ EmbeddedChannel channel = new EmbeddedChannel(new PacketCodec(new FirebirdPacketCodecEngine()));
+ channel.attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).set(StandardCharsets.UTF_8);
+ channel.writeInbound(Unpooled.buffer().writeInt(UNKNOWN_COMMAND_TYPE).writeInt(0));
+ channel.runPendingTasks();
+ assertNull(channel.readInbound());
+ ByteBuf encodedError = channel.readOutbound();
+ assertThat(encodedError.getInt(0), is(FirebirdCommandPacketType.RESPONSE.getValue()));
+ assertFalse(channel.isOpen());
+ encodedError.release();
+ }
+
+ @Test
+ void assertDecodeValidPacketFollowedByInvalidDiscardsValidPacket() {
+ EmbeddedChannel channel = new EmbeddedChannel(new PacketCodec(new FirebirdPacketCodecEngine()));
+ channel.attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).set(StandardCharsets.UTF_8);
+ ByteBuf in = Unpooled.buffer()
+ .writeInt(FirebirdCommandPacketType.ALLOCATE_STATEMENT.getValue()).writeInt(0)
+ .writeInt(UNKNOWN_COMMAND_TYPE).writeInt(0);
+ channel.writeInbound(in);
+ channel.runPendingTasks();
+ assertNull(channel.readInbound());
+ ByteBuf encodedError = channel.readOutbound();
+ assertThat(encodedError.getInt(0), is(FirebirdCommandPacketType.RESPONSE.getValue()));
+ assertFalse(channel.isOpen());
+ encodedError.release();
}
@Test
@@ -283,12 +335,33 @@ class FirebirdPacketCodecEngineTest {
}
@Test
- void assertDecodeBatchMessageWithoutRegisteredBatchStatementThrows() {
+ void assertDecodeBatchMessageWithoutRegisteredBatchStatementSendsErrorResponse() {
FirebirdBatchRegistry.getInstance().registerConnection(BATCH_CONNECTION_ID);
try {
ByteBuf in = buildBatchMessage(999, 100);
List<Object> out = new LinkedList<>();
- assertThrows(FirebirdProtocolException.class, () -> codecEngine.decode(context, in, out));
+ codecEngine.decode(context, in, out);
+ assertTrue(out.isEmpty());
+ assertThat(captureErrorResponse().getErrorCode(), is(335545159));
+ } finally {
+ FirebirdBatchRegistry.getInstance().unregisterConnection(BATCH_CONNECTION_ID);
+ }
+ }
+
+ @Test
+ void assertDecodeBatchMessageOverBufferSizeIsStillFramedWithoutCodecError() {
+ FirebirdBatchRegistry.getInstance().registerConnection(BATCH_CONNECTION_ID);
+ FirebirdBatchStatement batchStatement = new FirebirdBatchStatement(BATCH_STATEMENT_HANDLE,
+ Collections.singletonList(new FirebirdBatchColumnDescriptor(FirebirdBinaryColumnType.LONG, Integer.BYTES, 0, 0)), 8L);
+ batchStatement.addSize(1L);
+ FirebirdBatchRegistry.getInstance().registerBatchStatement(BATCH_CONNECTION_ID, BATCH_STATEMENT_HANDLE, batchStatement);
+ try {
+ ByteBuf in = buildBatchMessage(BATCH_STATEMENT_HANDLE, 100);
+ List<Object> out = new LinkedList<>();
+ codecEngine.decode(context, in, out);
+ assertThat(out.size(), is(1));
+ assertThat(createBatchSendMessagePacket((ByteBuf) out.iterator().next()).getStatementHandle(), is(BATCH_STATEMENT_HANDLE));
+ verify(context.channel(), never()).writeAndFlush(any());
} finally {
FirebirdBatchRegistry.getInstance().unregisterConnection(BATCH_CONNECTION_ID);
}
@@ -407,11 +480,16 @@ class FirebirdPacketCodecEngineTest {
}
@Test
- void assertDecodeCoalescedBatchCreateWithMalformedBlrClosesChannelAsFramingCorruption() {
+ void assertDecodeCoalescedBatchCreateWithMalformedBlrWritesErrorResponseAndClosesChannel() {
+ ChannelFuture errorResponseFuture = mock(ChannelFuture.class);
+ when(context.channel().writeAndFlush(any())).thenReturn(errorResponseFuture);
ByteBuf in = Unpooled.wrappedUnmodifiableBuffer(buildInvalidBatchCreate(), buildBatchMessage(BATCH_STATEMENT_HANDLE, 100));
List<Object> out = new LinkedList<>();
- assertThrows(IllegalArgumentException.class, () -> codecEngine.decode(context, in, out));
- verify(context).close();
+ codecEngine.decode(context, in, out);
+ assertTrue(out.isEmpty());
+ assertThat(in.readableBytes(), is(0));
+ assertThat(captureErrorResponse().getErrorCode(), is(335544382));
+ verify(errorResponseFuture).addListener(ChannelFutureListener.CLOSE);
}
private ByteBuf buildInvalidBatchCreate() {
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/err/FirebirdErrorPacketFactoryTest.java b/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdErrorPacketFactoryTest.java
similarity index 76%
rename from proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/err/FirebirdErrorPacketFactoryTest.java
rename to database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdErrorPacketFactoryTest.java
index 5bf1fae1753..d4f593fa358 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/err/FirebirdErrorPacketFactoryTest.java
+++ b/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdErrorPacketFactoryTest.java
@@ -15,10 +15,12 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.proxy.frontend.firebird.err;
+package org.apache.shardingsphere.database.protocol.firebird.err;
import org.apache.shardingsphere.database.exception.core.exception.connection.AccessDeniedException;
import org.apache.shardingsphere.database.exception.core.exception.syntax.database.UnknownDatabaseException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.BatchTooBigException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchHandleException;
import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket;
import org.junit.jupiter.api.Test;
@@ -57,6 +59,20 @@ class FirebirdErrorPacketFactoryTest {
assertThat(actual.getErrorMessage(), is(""));
}
+ @Test
+ void assertNewInstanceWithBatchTooBigException() {
+ FirebirdGenericResponsePacket actual = (FirebirdGenericResponsePacket) FirebirdErrorPacketFactory.newInstance(new BatchTooBigException(42, 1L, 8L, 8L));
+ assertThat(actual.getErrorCode(), is(335545198));
+ assertThat(actual.getErrorMessage(), is(""));
+ }
+
+ @Test
+ void assertNewInstanceWithInvalidBatchHandleException() {
+ FirebirdGenericResponsePacket actual = (FirebirdGenericResponsePacket) FirebirdErrorPacketFactory.newInstance(new InvalidBatchHandleException(42));
+ assertThat(actual.getErrorCode(), is(335545159));
+ assertThat(actual.getErrorMessage(), is(""));
+ }
+
@Test
void assertNewInstanceWithNonFirebirdErrorCode() {
FirebirdGenericResponsePacket actual = (FirebirdGenericResponsePacket) FirebirdErrorPacketFactory.newInstance(new SQLException("Invalid error", "HY000", 123));
diff --git a/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdErrorSegmentResolverTest.java b/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdErrorSegmentResolverTest.java
new file mode 100644
index 00000000000..1dc154e6cca
--- /dev/null
+++ b/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdErrorSegmentResolverTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.firebird.err;
+
+import org.apache.shardingsphere.database.protocol.firebird.err.FirebirdStatusVector.Segment;
+import org.firebirdsql.gds.GDSExceptionHelper;
+import org.firebirdsql.gds.ISCConstants;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class FirebirdErrorSegmentResolverTest {
+
+ @Test
+ void assertResolveRebuildsDuplicateKeyVectorAsSegments() {
+ String message = "violation of PRIMARY or UNIQUE KEY constraint \"INTEG_2\" on table \"MY_TABLE\";"
+ + " Problematic key value is (\"COL1\" = 1)";
+ List<Segment> segments = FirebirdErrorSegmentResolver.resolve(ISCConstants.isc_unique_key_violation, message, "23000");
+ assertThat(segments.size(), is(2));
+ assertThat(segments.get(0).getGdsCode(), is(ISCConstants.isc_unique_key_violation));
+ assertThat(segments.get(0).getArguments(), contains("INTEG_2", "MY_TABLE"));
+ assertNull(segments.get(0).getSqlState());
+ assertThat(segments.get(1).getGdsCode(), is(ISCConstants.isc_random));
+ assertThat(segments.get(1).getArguments(), contains("Problematic key value is (\"COL1\" = 1)"));
+ }
+
+ @Test
+ void assertResolveRendersDuplicateKeyTableNameWithoutNull() {
+ String message = "violation of PRIMARY or UNIQUE KEY constraint \"INTEG_2\" on table \"MY_TABLE\";"
+ + " Problematic key value is (\"COL1\" = 1)";
+ List<Segment> segments = FirebirdErrorSegmentResolver.resolve(ISCConstants.isc_unique_key_violation, message, "23000");
+ StringBuilder rendered = new StringBuilder();
+ for (Segment each : segments) {
+ GDSExceptionHelper.GDSMessage segmentMessage = GDSExceptionHelper.getMessage(each.getGdsCode());
+ segmentMessage.setParameters(each.getArguments());
+ if (rendered.length() > 0) {
+ rendered.append("; ");
+ }
+ rendered.append(segmentMessage);
+ }
+ assertThat(rendered.toString(), is(message));
+ }
+
+ @Test
+ void assertResolveUsesRandomCodeWhenErrorCodeIsLowerThanArithExcept() {
+ List<Segment> segments = FirebirdErrorSegmentResolver.resolve(ISCConstants.isc_arith_except - 1, "plain", "00000");
+ assertThat(segments.size(), is(1));
+ assertThat(segments.get(0).getGdsCode(), is(ISCConstants.isc_random));
+ assertThat(segments.get(0).getArguments(), is(Arrays.asList("plain")));
+ }
+
+ @Test
+ void assertResolveFailSafeUsesRandomCodeAndKeepsSqlStateForUnmatchedParameterizedTemplate() {
+ List<Segment> segments = FirebirdErrorSegmentResolver.resolve(ISCConstants.isc_unique_key_violation, "totally unrelated text", "23000");
+ assertThat(segments.size(), is(1));
+ assertThat(segments.get(0).getGdsCode(), is(ISCConstants.isc_random));
+ assertThat(segments.get(0).getArguments(), contains("totally unrelated text"));
+ assertThat(segments.get(0).getSqlState(), is("23000"));
+ }
+
+ @Test
+ void assertResolveKeepsBatchParameterVersionCodeWithBothArguments() {
+ List<Segment> segments = FirebirdErrorSegmentResolver.resolve(ISCConstants.isc_batch_param_version, "Wrong version of batch parameters block 2, should be 1", "22000");
+ assertThat(segments.size(), is(1));
+ assertThat(segments.get(0).getGdsCode(), is(ISCConstants.isc_batch_param_version));
+ assertThat(segments.get(0).getArguments(), contains("2", "1"));
+ assertNull(segments.get(0).getSqlState());
+ }
+
+ @Test
+ void assertResolveKeepsBatchParamCode() {
+ List<Segment> segments = FirebirdErrorSegmentResolver.resolve(ISCConstants.isc_batch_param, "Statement used in batch must have parameters", "07001");
+ assertThat(segments.size(), is(1));
+ assertThat(segments.get(0).getGdsCode(), is(ISCConstants.isc_batch_param));
+ assertTrue(segments.get(0).getArguments().isEmpty());
+ assertNull(segments.get(0).getSqlState());
+ }
+}
diff --git a/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdStatusVectorTest.java b/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdStatusVectorTest.java
index 82aaa361496..0501908ee9b 100644
--- a/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdStatusVectorTest.java
+++ b/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/err/FirebirdStatusVectorTest.java
@@ -27,8 +27,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
import java.sql.SQLException;
-import static org.hamcrest.Matchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -39,25 +39,48 @@ class FirebirdStatusVectorTest {
private FirebirdPacketPayload payload;
@Test
- void assertWriteWithArithmeticCodeAndTrimmedMessage() {
- SQLException richMessage = new SQLException("prefix; detail [SQLState:42000]", "42000", ISCConstants.isc_arith_except);
- FirebirdStatusVector vector = new FirebirdStatusVector(richMessage);
- assertThat(vector.getGdsCode(), is(richMessage.getErrorCode()));
- assertThat(vector.getErrorMessage(), is("detail"));
+ void assertWriteKeepsNativeCodeAndStripsStateSuffix() {
+ SQLException ex = new SQLException("prefix; detail [SQLState:42000]", "42000", ISCConstants.isc_arith_except);
+ FirebirdStatusVector vector = new FirebirdStatusVector(ex);
+ assertThat(vector.getGdsCode(), is(ISCConstants.isc_arith_except));
+ assertThat(vector.getErrorMessage(), is("prefix; detail"));
vector.write(payload);
InOrder inOrder = inOrder(payload);
inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_gds);
inOrder.verify(payload).writeInt4(ISCConstants.isc_arith_except);
inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_string);
- inOrder.verify(payload).writeString("detail");
+ inOrder.verify(payload).writeString("prefix; detail");
+ inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_end);
+ verifyNoMoreInteractions(payload);
+ }
+
+ @Test
+ void assertWriteRebuildsDuplicateKeyVectorAsSegments() {
+ String message = "violation of PRIMARY or UNIQUE KEY constraint \"INTEG_2\" on table \"MY_TABLE\";"
+ + " Problematic key value is (\"COL1\" = 1) [SQLState:23000, ISC error code:335544665]";
+ SQLException ex = new SQLException(message, "23000", ISCConstants.isc_unique_key_violation);
+ FirebirdStatusVector vector = new FirebirdStatusVector(ex);
+ assertThat(vector.getGdsCode(), is(ISCConstants.isc_unique_key_violation));
+ vector.write(payload);
+ InOrder inOrder = inOrder(payload);
+ inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_gds);
+ inOrder.verify(payload).writeInt4(ISCConstants.isc_unique_key_violation);
+ inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_string);
+ inOrder.verify(payload).writeString("INTEG_2");
+ inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_string);
+ inOrder.verify(payload).writeString("MY_TABLE");
+ inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_gds);
+ inOrder.verify(payload).writeInt4(ISCConstants.isc_random);
+ inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_string);
+ inOrder.verify(payload).writeString("Problematic key value is (\"COL1\" = 1)");
inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_end);
verifyNoMoreInteractions(payload);
}
@Test
void assertWriteOmitsArgumentWhenMessageIsEmpty() {
- SQLException emptyMessage = new SQLException("", "28000", ISCConstants.isc_login);
- FirebirdStatusVector vector = new FirebirdStatusVector(emptyMessage);
+ SQLException ex = new SQLException("", "28000", ISCConstants.isc_login);
+ FirebirdStatusVector vector = new FirebirdStatusVector(ex);
vector.write(payload);
InOrder inOrder = inOrder(payload);
inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_gds);
@@ -68,8 +91,8 @@ class FirebirdStatusVectorTest {
@Test
void assertWriteWithNullMessage() {
- SQLException nullMessage = new SQLException(null, "28000", ISCConstants.isc_login);
- FirebirdStatusVector vector = new FirebirdStatusVector(nullMessage);
+ SQLException ex = new SQLException(null, "28000", ISCConstants.isc_login);
+ FirebirdStatusVector vector = new FirebirdStatusVector(ex);
assertThat(vector.getErrorMessage(), is(""));
vector.write(payload);
InOrder inOrder = inOrder(payload);
@@ -81,8 +104,9 @@ class FirebirdStatusVectorTest {
@Test
void assertWriteUsesRandomCodeWhenErrorCodeIsLowerThanArithExcept() {
- SQLException plainMessage = new SQLException("plain", "00000", ISCConstants.isc_arith_except - 1);
- FirebirdStatusVector vector = new FirebirdStatusVector(plainMessage);
+ SQLException ex = new SQLException("plain", "00000", ISCConstants.isc_arith_except - 1);
+ FirebirdStatusVector vector = new FirebirdStatusVector(ex);
+ assertThat(vector.getGdsCode(), is(ISCConstants.isc_random));
vector.write(payload);
InOrder inOrder = inOrder(payload);
inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_gds);
@@ -92,4 +116,21 @@ class FirebirdStatusVectorTest {
inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_end);
verifyNoMoreInteractions(payload);
}
+
+ @Test
+ void assertWriteFailSafeUsesRandomCodeAndKeepsSqlStateForUnmatchedParameterizedTemplate() {
+ SQLException ex = new SQLException("totally unrelated text", "23000", ISCConstants.isc_unique_key_violation);
+ FirebirdStatusVector vector = new FirebirdStatusVector(ex);
+ assertThat(vector.getGdsCode(), is(ISCConstants.isc_random));
+ vector.write(payload);
+ InOrder inOrder = inOrder(payload);
+ inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_gds);
+ inOrder.verify(payload).writeInt4(ISCConstants.isc_random);
+ inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_string);
+ inOrder.verify(payload).writeString("totally unrelated text");
+ inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_sql_state);
+ inOrder.verify(payload).writeString("23000");
+ inOrder.verify(payload).writeInt4(ISCConstants.isc_arg_end);
+ verifyNoMoreInteractions(payload);
+ }
}
diff --git a/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchCommandPacketTest.java b/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchCommandPacketTest.java
index d063d534ae8..617199c497e 100644
--- a/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchCommandPacketTest.java
+++ b/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchCommandPacketTest.java
@@ -19,7 +19,7 @@ package org.apache.shardingsphere.database.protocol.firebird.packet.command.quer
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
-import org.apache.shardingsphere.database.protocol.firebird.exception.FirebirdProtocolException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchHandleException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.FirebirdBinaryColumnType;
import org.apache.shardingsphere.database.protocol.firebird.payload.FirebirdPacketPayload;
import org.junit.jupiter.api.Test;
@@ -66,7 +66,7 @@ class FirebirdBatchCommandPacketTest {
@Test
void assertBatchSendMessageGetLengthWithoutBatchStatement() {
FirebirdPacketPayload payload = new FirebirdPacketPayload(Unpooled.buffer().writeZero(4).writeInt(1).writeInt(1), StandardCharsets.UTF_8);
- assertThrows(FirebirdProtocolException.class, () -> FirebirdBatchMessageCommandPacket.getLength(payload, CONNECTION_ID));
+ assertThrows(InvalidBatchHandleException.class, () -> FirebirdBatchMessageCommandPacket.getLength(payload, CONNECTION_ID));
}
@Test
diff --git a/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchStatementTest.java b/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchStatementTest.java
index a57fe04348f..26cc1f8d51b 100644
--- a/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchStatementTest.java
+++ b/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchStatementTest.java
@@ -35,17 +35,26 @@ class FirebirdBatchStatementTest {
batchStatement.addParameterValues(Arrays.asList("foo", 1L));
assertThat(batchStatement.getStatementHandle(), is(32));
assertFalse(batchStatement.isRecordCounts());
+ assertFalse(batchStatement.isMultiError());
assertThat(batchStatement.getParameterValues().size(), is(1));
assertThat(batchStatement.getParameterValues().get(0), is(Arrays.asList("foo", 1L)));
}
@Test
void assertCreateWithRecordCounts() {
- FirebirdBatchStatement batchStatement = new FirebirdBatchStatement(32, Collections.emptyList(), 1024L, true);
+ FirebirdBatchStatement batchStatement = new FirebirdBatchStatement(32, Collections.emptyList(), 1024L, true, false);
assertTrue(batchStatement.isRecordCounts());
+ assertFalse(batchStatement.isMultiError());
assertThat(batchStatement.getBufferSize(), is(1024L));
}
+ @Test
+ void assertCreateWithMultiError() {
+ FirebirdBatchStatement batchStatement = new FirebirdBatchStatement(32, Collections.emptyList(), 1024L, false, true);
+ assertTrue(batchStatement.isMultiError());
+ assertFalse(batchStatement.isRecordCounts());
+ }
+
@Test
void assertReset() {
FirebirdBatchStatement batchStatement = new FirebirdBatchStatement(100);
diff --git a/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/packet/generic/FirebirdBatchCompletionStateResponseTest.java b/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/packet/generic/FirebirdBatchCompletionStateResponseTest.java
index 9f570d0c38a..ea2ef80953f 100644
--- a/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/packet/generic/FirebirdBatchCompletionStateResponseTest.java
+++ b/database/protocol/dialect/firebird/src/test/java/org/apache/shardingsphere/database/protocol/firebird/packet/generic/FirebirdBatchCompletionStateResponseTest.java
@@ -17,6 +17,7 @@
package org.apache.shardingsphere.database.protocol.firebird.packet.generic;
+import org.apache.shardingsphere.database.protocol.firebird.err.FirebirdStatusVector;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.FirebirdCommandPacketType;
import org.apache.shardingsphere.database.protocol.firebird.payload.FirebirdPacketPayload;
import org.junit.jupiter.api.Test;
@@ -51,4 +52,16 @@ class FirebirdBatchCompletionStateResponseTest {
assertThat(argumentCaptor.getAllValues(),
is(Arrays.asList(FirebirdCommandPacketType.BATCH_CS.getValue(), 0, 0, 0, 0, 0)));
}
+
+ @Test
+ void assertWriteWithDetailedError() {
+ FirebirdPacketPayload payload = mock(FirebirdPacketPayload.class);
+ FirebirdStatusVector statusVector = mock(FirebirdStatusVector.class);
+ new FirebirdBatchCompletionStateResponse().setHandle(7).setRecordsCount(3L).setUpdateCounts(new int[]{1}).addDetailedError(0, statusVector).write(payload);
+ ArgumentCaptor<Integer> argumentCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(payload, atLeastOnce()).writeInt4(argumentCaptor.capture());
+ assertThat(argumentCaptor.getAllValues(),
+ is(Arrays.asList(FirebirdCommandPacketType.BATCH_CS.getValue(), 7, 3, 1, 1, 0, 1, 0)));
+ verify(statusVector).write(payload);
+ }
}
diff --git a/parser/sql/engine/dialect/firebird/src/main/antlr4/imports/firebird/DDLStatement.g4 b/parser/sql/engine/dialect/firebird/src/main/antlr4/imports/firebird/DDLStatement.g4
index 573760eeca1..ef9b2f95b5b 100644
--- a/parser/sql/engine/dialect/firebird/src/main/antlr4/imports/firebird/DDLStatement.g4
+++ b/parser/sql/engine/dialect/firebird/src/main/antlr4/imports/firebird/DDLStatement.g4
@@ -190,11 +190,11 @@ inputArgumentClause
;
createDatabase
- : CREATE SCHEMA schemaName createDatabaseSpecification_*
+ : CREATE (DATABASE | SCHEMA) STRING_ createDatabaseSpecification_*
;
dropDatabase
- : DROP SCHEMA schemaName dropBehaviour
+ : DROP DATABASE
;
createView
diff --git a/parser/sql/engine/dialect/firebird/src/main/java/org/apache/shardingsphere/sql/parser/engine/firebird/visitor/statement/type/FirebirdDDLStatementVisitor.java b/parser/sql/engine/dialect/firebird/src/main/java/org/apache/shardingsphere/sql/parser/engine/firebird/visitor/statement/type/FirebirdDDLStatementVisitor.java
index d4eae29bbc8..9a44fdb4d69 100644
--- a/parser/sql/engine/dialect/firebird/src/main/java/org/apache/shardingsphere/sql/parser/engine/firebird/visitor/statement/type/FirebirdDDLStatementVisitor.java
+++ b/parser/sql/engine/dialect/firebird/src/main/java/org/apache/shardingsphere/sql/parser/engine/firebird/visitor/statement/type/FirebirdDDLStatementVisitor.java
@@ -36,6 +36,7 @@ import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.Comm
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.ConstraintDefinitionContext;
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.ConstraintNameContext;
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.CreateCollationContext;
+import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.CreateDatabaseContext;
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.CreateDefinitionClauseContext;
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.CreateDefinitionContext;
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.CreateDomainContext;
@@ -47,6 +48,7 @@ import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.Crea
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.DataTypeOptionContext;
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.DropColumnSpecificationContext;
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.DropConstraintSpecificationContext;
+import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.DropDatabaseContext;
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.DropTableContext;
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.ExecuteStmtContext;
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.ModifyColumnSpecificationContext;
@@ -67,6 +69,8 @@ import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table
import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.CommentStatement;
import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.ExecuteStatement;
import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.collation.CreateCollationStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.database.CreateDatabaseStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.database.DropDatabaseStatement;
import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.domain.AlterDomainStatement;
import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.domain.CreateDomainStatement;
import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.function.CreateFunctionStatement;
@@ -262,6 +266,24 @@ public final class FirebirdDDLStatementVisitor extends FirebirdStatementVisitor
return new DropTableStatement(getDatabaseType(), ((CollectionValue<SimpleTableSegment>) visit(ctx.tableNames())).getValue(), false, false);
}
+ @Override
+ public ASTNode visitCreateDatabase(final CreateDatabaseContext ctx) {
+ return new CreateDatabaseStatement(getDatabaseType(), extractDatabaseName(ctx.STRING_().getText()), false);
+ }
+
+ @Override
+ public ASTNode visitDropDatabase(final DropDatabaseContext ctx) {
+ return new DropDatabaseStatement(getDatabaseType(), null, false);
+ }
+
+ private String extractDatabaseName(final String filespec) {
+ String result = filespec.substring(1, filespec.length() - 1);
+ int separatorIndex = Math.max(Math.max(result.lastIndexOf('/'), result.lastIndexOf('\\')), result.lastIndexOf(':'));
+ result = separatorIndex >= 0 ? result.substring(separatorIndex + 1) : result;
+ int extensionIndex = result.lastIndexOf('.');
+ return extensionIndex > 0 ? result.substring(0, extensionIndex) : result;
+ }
+
@Override
public ASTNode visitCreateFunction(final CreateFunctionContext ctx) {
return new CreateFunctionStatement(getDatabaseType(), null, null, Collections.emptyList());
diff --git a/parser/sql/engine/dialect/firebird/src/main/java/org/apache/shardingsphere/sql/parser/engine/firebird/visitor/statement/type/FirebirdDMLStatementVisitor.java b/parser/sql/engine/dialect/firebird/src/main/java/org/apache/shardingsphere/sql/parser/engine/firebird/visitor/statement/type/FirebirdDMLStatementVisitor.java
index c4dc34b82c9..a93cbdb790b 100644
--- a/parser/sql/engine/dialect/firebird/src/main/java/org/apache/shardingsphere/sql/parser/engine/firebird/visitor/statement/type/FirebirdDMLStatementVisitor.java
+++ b/parser/sql/engine/dialect/firebird/src/main/java/org/apache/shardingsphere/sql/parser/engine/firebird/visitor/statement/type/FirebirdDMLStatementVisitor.java
@@ -17,10 +17,14 @@
package org.apache.shardingsphere.sql.parser.engine.firebird.visitor.statement.type;
+import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.misc.Interval;
+import org.antlr.v4.runtime.tree.ParseTree;
+import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.sql.parser.api.ASTNode;
import org.apache.shardingsphere.sql.parser.api.visitor.statement.type.DMLStatementVisitor;
+import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser;
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.AliasContext;
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.AssignmentContext;
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.AssignmentValueContext;
@@ -59,6 +63,7 @@ import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.Tabl
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.UpdateContext;
import org.apache.shardingsphere.sql.parser.autogen.FirebirdStatementParser.WhereClauseContext;
import org.apache.shardingsphere.sql.parser.engine.firebird.visitor.statement.FirebirdStatementVisitor;
+import org.apache.shardingsphere.sql.parser.statement.core.enums.CombineType;
import org.apache.shardingsphere.sql.parser.statement.core.enums.JoinType;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.ReturningSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.assignment.ColumnAssignmentSegment;
@@ -66,6 +71,7 @@ import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.assignmen
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.assignment.SetAssignmentSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.InsertColumnsSegment;
+import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.combine.CombineSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.BinaryOperationExpression;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.ExpressionSegment;
import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.FunctionSegment;
@@ -261,8 +267,45 @@ public final class FirebirdDMLStatementVisitor extends FirebirdStatementVisitor
@Override
public ASTNode visitCombineClause(final CombineClauseContext ctx) {
- // TODO :Unsupported for union SQL.
- return visit(ctx.selectClause(0));
+ SelectStatement result = (SelectStatement) visit(ctx.selectClause(0));
+ if (1 == ctx.selectClause().size()) {
+ return result;
+ }
+ SelectClauseContext firstContext = ctx.selectClause(0);
+ SubquerySegment left = new SubquerySegment(firstContext.start.getStartIndex(), firstContext.stop.getStopIndex(), result, getOriginalText(firstContext));
+ for (int i = 1; i < ctx.selectClause().size(); i++) {
+ SelectClauseContext rightContext = ctx.selectClause(i);
+ SelectStatement rightSelect = (SelectStatement) visit(rightContext);
+ boolean liftTrailingOrderBy = i == ctx.selectClause().size() - 1 && null != rightContext.orderByClause();
+ OrderBySegment trailingOrderBy = liftTrailingOrderBy ? rightSelect.getOrderBy().orElse(null) : null;
+ if (null != trailingOrderBy) {
+ rightSelect = createSelectStatementBuilder(rightSelect).orderBy(null).build();
+ }
+ SubquerySegment right = createCombineRightSubquery(rightContext, rightSelect, null != trailingOrderBy);
+ result = SelectStatement.builder().databaseType(getDatabaseType()).projections(left.getSelect().getProjections())
+ .from(left.getSelect().getFrom().orElse(null))
+ .combine(new CombineSegment(left.getStartIndex(), right.getStopIndex(), left, getCombineType(ctx, rightContext), right))
+ .orderBy(trailingOrderBy).build();
+ left = new SubquerySegment(left.getStartIndex(), right.getStopIndex(), result, ctx.start.getInputStream().getText(new Interval(left.getStartIndex(), right.getStopIndex())));
+ }
+ return result;
+ }
+
+ private CombineType getCombineType(final CombineClauseContext ctx, final SelectClauseContext rightContext) {
+ ParseTree previous = ctx.getChild(ctx.children.indexOf(rightContext) - 1);
+ boolean isUnionAll = previous instanceof TerminalNode && FirebirdStatementParser.ALL == ((TerminalNode) previous).getSymbol().getType();
+ return isUnionAll ? CombineType.UNION_ALL : CombineType.UNION;
+ }
+
+ private SubquerySegment createCombineRightSubquery(final SelectClauseContext rightContext, final SelectStatement rightSelect, final boolean excludeTrailingOrderBy) {
+ int stopIndex = excludeTrailingOrderBy ? getStopIndexBefore(rightContext, rightContext.orderByClause()) : rightContext.stop.getStopIndex();
+ return new SubquerySegment(rightContext.start.getStartIndex(), stopIndex, rightSelect,
+ rightContext.start.getInputStream().getText(new Interval(rightContext.start.getStartIndex(), stopIndex)));
+ }
+
+ private int getStopIndexBefore(final ParserRuleContext parent, final ParserRuleContext child) {
+ ParseTree previous = parent.getChild(parent.children.indexOf(child) - 1);
+ return previous instanceof TerminalNode ? ((TerminalNode) previous).getSymbol().getStopIndex() : ((ParserRuleContext) previous).stop.getStopIndex();
}
@Override
diff --git a/proxy/backend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/backend/firebird/handler/admin/executor/variable/charset/FirebirdCharsetVariableProvider.java b/proxy/backend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/backend/firebird/handler/admin/executor/variable/charset/FirebirdCharsetVariableProvider.java
index 3da398036da..ac6fceaa95e 100644
--- a/proxy/backend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/backend/firebird/handler/admin/executor/variable/charset/FirebirdCharsetVariableProvider.java
+++ b/proxy/backend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/backend/firebird/handler/admin/executor/variable/charset/FirebirdCharsetVariableProvider.java
@@ -40,7 +40,7 @@ public final class FirebirdCharsetVariableProvider implements CharsetVariablePro
String formattedValue = variableValue.trim().toLowerCase(Locale.ROOT);
try {
return "default".equals(formattedValue) ? Charset.defaultCharset() : FirebirdCharacterSets.findCharacterSet(formattedValue);
- } catch (final IllegalArgumentException ignored) {
+ } catch (final IllegalArgumentException ex) {
throw new InvalidParameterValueException("names", formattedValue);
}
}
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngine.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngine.java
index 9ce0770a9f8..70f28481dc4 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngine.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngine.java
@@ -25,6 +25,7 @@ import org.apache.shardingsphere.authentication.result.AuthenticationResult;
import org.apache.shardingsphere.authentication.result.AuthenticationResultBuilder;
import org.apache.shardingsphere.authority.rule.AuthorityRule;
import org.apache.shardingsphere.database.exception.core.exception.connection.AccessDeniedException;
+import org.apache.shardingsphere.database.exception.core.exception.data.InvalidParameterValueException;
import org.apache.shardingsphere.database.exception.core.exception.syntax.database.UnknownDatabaseException;
import org.apache.shardingsphere.database.protocol.constant.CommonConstants;
import org.apache.shardingsphere.database.protocol.firebird.constant.FirebirdAuthenticationMethod;
@@ -56,6 +57,7 @@ import org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement.fetch.FirebirdFetchStatementCache;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction.FirebirdTransactionIdGenerator;
+import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
@@ -106,12 +108,23 @@ public final class FirebirdAuthenticationEngine implements AuthenticationEngine
private AuthenticationResult processAttach(final ChannelHandlerContext context, final FirebirdPacketPayload payload, final AuthorityRule rule) {
FirebirdAttachPacket attachPacket = new FirebirdAttachPacket(payload);
- context.channel().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).set(FirebirdCharacterSets.findCharacterSet(attachPacket.getEncoding()));
+ context.channel().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).set(parseAttachCharset(attachPacket.getEncoding()));
login(currentAuthResult.getDatabase(), currentAuthResult.getUsername(), attachPacket, rule);
context.writeAndFlush(new FirebirdGenericResponsePacket());
return AuthenticationResultBuilder.finished(currentAuthResult.getUsername(), "", currentAuthResult.getDatabase(), currentAuthResult.getConnectionAttributes());
}
+ private Charset parseAttachCharset(final String encoding) {
+ if (null == encoding) {
+ return FirebirdCharacterSets.findCharacterSet("NONE");
+ }
+ try {
+ return FirebirdCharacterSets.findCharacterSet(encoding);
+ } catch (final IllegalArgumentException ex) {
+ throw new InvalidParameterValueException("lc_ctype", encoding);
+ }
+ }
+
private void login(final String databaseName, final String username, final FirebirdAttachPacket attachPacket, final AuthorityRule rule) {
ShardingSpherePreconditions.checkState(
Strings.isNullOrEmpty(databaseName) || ProxyContext.getInstance().getContextManager().getMetaDataContexts().getMetaData().containsDatabase(databaseName),
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/FirebirdCommandExecuteEngine.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/FirebirdCommandExecuteEngine.java
index e3ab746148e..80b08967d4d 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/FirebirdCommandExecuteEngine.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/FirebirdCommandExecuteEngine.java
@@ -19,6 +19,7 @@ package org.apache.shardingsphere.proxy.frontend.firebird.command;
import io.netty.channel.ChannelHandlerContext;
import org.apache.shardingsphere.database.protocol.firebird.constant.protocol.FirebirdConnectionProtocolVersion;
+import org.apache.shardingsphere.database.protocol.firebird.err.FirebirdErrorPacketFactory;
import org.apache.shardingsphere.database.protocol.firebird.packet.FirebirdPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.FirebirdCommandPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.FirebirdCommandPacketFactory;
@@ -36,7 +37,6 @@ import org.apache.shardingsphere.proxy.frontend.command.CommandExecuteEngine;
import org.apache.shardingsphere.proxy.frontend.command.executor.CommandExecutor;
import org.apache.shardingsphere.proxy.frontend.command.executor.QueryCommandExecutor;
import org.apache.shardingsphere.proxy.frontend.command.executor.ResponseType;
-import org.apache.shardingsphere.proxy.frontend.firebird.err.FirebirdErrorPacketFactory;
import java.sql.SQLException;
import java.util.Optional;
diff --git a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCompletion.java
similarity index 52%
copy from database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
copy to proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCompletion.java
index f00a726c59e..9d3f90a984f 100644
--- a/database/exception/dialect/firebird/src/main/java/org/apache/shardingsphere/database/exception/firebird/sqlstate/FirebirdState.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCompletion.java
@@ -15,22 +15,43 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.database.exception.firebird.sqlstate;
+package org.apache.shardingsphere.proxy.frontend.firebird.command.query.batch;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.infra.exception.external.sql.sqlstate.SQLState;
+
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Collections;
/**
- * Firebird SQL state.
- *
- * <p>Holds SQL states that Jaybird derives from a GDSCODE but that are not defined in {@code XOpenSQLState}.</p>
+ * Batch completion result for Firebird.
*/
@RequiredArgsConstructor
@Getter
-public enum FirebirdState implements SQLState {
+public final class FirebirdBatchCompletion {
+
+ public static final int EXECUTE_FAILED = -1;
+
+ private final int recordsCount;
+
+ private final int[] updateCounts;
+
+ private final Collection<Failure> failures;
- UNAVAILABLE_DATABASE("08001");
+ public FirebirdBatchCompletion(final int recordsCount, final int[] updateCounts) {
+ this(recordsCount, updateCounts, Collections.emptyList());
+ }
- private final String value;
+ /**
+ * Failure of a batch message.
+ */
+ @RequiredArgsConstructor
+ @Getter
+ public static final class Failure {
+
+ private final int messageIndex;
+
+ private final SQLException cause;
+ }
}
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCreateCommandExecutor.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCreateCommandExecutor.java
index 8e198193402..1cb7d04f98f 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCreateCommandExecutor.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCreateCommandExecutor.java
@@ -20,6 +20,11 @@ package org.apache.shardingsphere.proxy.frontend.firebird.command.query.batch;
import io.netty.buffer.ByteBuf;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.BatchAlreadyOpenedException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.BatchParametersRequiredException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchMessageFormatException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchParameterVersionException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidStatementHandleException;
import org.apache.shardingsphere.database.protocol.firebird.exception.FirebirdProtocolException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchCreateCommandPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdParseBatchBlr;
@@ -46,6 +51,8 @@ public final class FirebirdBatchCreateCommandExecutor implements CommandExecutor
private static final int BATCH_VERSION_1 = 1;
+ private static final int TAG_MULTIERROR = 1;
+
private static final int TAG_RECORD_COUNTS = 2;
private static final int TAG_BUFFER_BYTES_SIZE = 3;
@@ -67,24 +74,25 @@ public final class FirebirdBatchCreateCommandExecutor implements CommandExecutor
int connectionId = connectionSession.getConnectionId();
int statementId = packet.getStatementHandle();
if (null == connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(statementId)) {
- throw new FirebirdProtocolException("Statement handle %d was not found", statementId);
+ throw new InvalidStatementHandleException(statementId);
}
if (null != FirebirdBatchRegistry.getInstance().getBatchStatement(connectionId, statementId)) {
- throw new FirebirdProtocolException("Batch already open for statement handle %d", statementId);
+ throw new BatchAlreadyOpenedException(statementId);
}
ByteBuf batchBlr = packet.getBatchBlr();
int blrLength = batchBlr.readableBytes();
FirebirdParseBatchBlr messageFormat = FirebirdParseBatchBlr.parse(batchBlr, blrLength);
if (messageFormat.getFields().isEmpty()) {
- throw new FirebirdProtocolException("Batch message format must contain at least one field");
+ throw new BatchParametersRequiredException(statementId);
}
if (packet.getBatchMessageLength() != messageFormat.getMessageLength()) {
- throw new FirebirdProtocolException("Invalid message length: computed %d from BLR but client sent %d", messageFormat.getMessageLength(), packet.getBatchMessageLength());
+ throw new InvalidBatchMessageFormatException(
+ String.format("invalid message length: computed %d from BLR but client sent %d", messageFormat.getMessageLength(), packet.getBatchMessageLength()));
}
ByteBuf batchParametersBuffer = packet.getBatchParametersBuffer();
BatchParameters batchParameters = BatchParameters.parse(batchParametersBuffer);
FirebirdBatchRegistry.getInstance().registerBatchStatement(connectionId, statementId,
- new FirebirdBatchStatement(statementId, messageFormat.getFields(), batchParameters.getBufferSize(), batchParameters.isRecordCounts()));
+ new FirebirdBatchStatement(statementId, messageFormat.getFields(), batchParameters.getBufferSize(), batchParameters.isRecordCounts(), batchParameters.isMultiError()));
return Collections.singleton(new FirebirdGenericResponsePacket().setHandle(statementId));
}
@@ -98,27 +106,32 @@ public final class FirebirdBatchCreateCommandExecutor implements CommandExecutor
private final boolean recordCounts;
+ private final boolean multiError;
+
// private final int blobPolicy;
static BatchParameters parse(final ByteBuf batchParametersBuffer) {
if (null == batchParametersBuffer || !batchParametersBuffer.isReadable()) {
- // return new BatchParameters(BATCH_VERSION_1, DEFAULT_BUFFER_SIZE, false, BLOB_STREAM);
- return new BatchParameters(BATCH_VERSION_1, DEFAULT_BUFFER_SIZE, false);
+ // return new BatchParameters(BATCH_VERSION_1, DEFAULT_BUFFER_SIZE, false, false, BLOB_STREAM);
+ return new BatchParameters(BATCH_VERSION_1, DEFAULT_BUFFER_SIZE, false, false);
}
ByteBuf reader = batchParametersBuffer.duplicate();
int version = reader.readUnsignedByte();
if (BATCH_VERSION_1 != version) {
- throw new FirebirdProtocolException("Invalid batch parameters version: %d", version);
+ throw new InvalidBatchParameterVersionException(version, BATCH_VERSION_1);
}
long bufferSize = DEFAULT_BUFFER_SIZE;
boolean recordCounts = false;
+ boolean multiError = false;
// int blobPolicy = BLOB_STREAM;
while (reader.isReadable()) {
ensureClumpletHeaderReadable(reader);
int tag = reader.readUnsignedByte();
int valueLength = reader.readIntLE();
ensureClumpletValueReadable(reader, tag, valueLength);
- if (TAG_RECORD_COUNTS == tag) {
+ if (TAG_MULTIERROR == tag) {
+ multiError = 0 != readIntegerValue(reader, tag, valueLength);
+ } else if (TAG_RECORD_COUNTS == tag) {
recordCounts = 0 != readIntegerValue(reader, tag, valueLength);
} else if (TAG_BUFFER_BYTES_SIZE == tag) {
bufferSize = getBufferSize(readIntegerValue(reader, tag, valueLength));
@@ -131,8 +144,8 @@ public final class FirebirdBatchCreateCommandExecutor implements CommandExecutor
reader.skipBytes(valueLength);
}
}
- // return new BatchParameters(version, bufferSize, recordCounts, blobPolicy);
- return new BatchParameters(version, bufferSize, recordCounts);
+ // return new BatchParameters(version, bufferSize, recordCounts, multiError, blobPolicy);
+ return new BatchParameters(version, bufferSize, recordCounts, multiError);
}
private static long getBufferSize(final int requestedBufferSize) {
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchExecuteCommandExecutor.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchExecuteCommandExecutor.java
index 0ab4b6fe6e3..24093278197 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchExecuteCommandExecutor.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchExecuteCommandExecutor.java
@@ -18,15 +18,19 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.batch;
import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.database.protocol.firebird.exception.FirebirdProtocolException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchHandleException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidTransactionHandleException;
+import org.apache.shardingsphere.database.protocol.firebird.err.FirebirdStatusVector;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchExecuteCommandPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchRegistry;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchStatement;
import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdBatchCompletionStateResponse;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
+import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
import org.apache.shardingsphere.proxy.frontend.command.executor.CommandExecutor;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.FirebirdServerPreparedStatement;
+import org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction.FirebirdTransactionIdGenerator;
import java.sql.SQLException;
import java.util.Collection;
@@ -43,17 +47,37 @@ public final class FirebirdBatchExecuteCommandExecutor implements CommandExecuto
public Collection<DatabasePacket> execute() throws SQLException {
FirebirdBatchStatement batchStatement = FirebirdBatchRegistry.getInstance().getBatchStatement(connectionSession.getConnectionId(), packet.getStatementHandle());
if (null == batchStatement) {
- throw new FirebirdProtocolException("Batch statement not found for connectionId: %d, statement handle: %d", connectionSession.getConnectionId(), packet.getStatementHandle());
+ throw new InvalidBatchHandleException(packet.getStatementHandle());
}
- int[] updateCounts = new int[0];
- if (!batchStatement.getParameterValues().isEmpty()) {
- FirebirdServerPreparedStatement preparedStatement = connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(batchStatement.getStatementHandle());
- updateCounts = new FirebirdBatchedStatementsExecutor(connectionSession, preparedStatement, batchStatement.getParameterValues()).executeBatch();
+ validateTransactionHandle();
+ int messageCount = batchStatement.getParameterValues().size();
+ if (batchStatement.getParameterValues().isEmpty()) {
+ batchStatement.reset();
+ return Collections.singleton(new FirebirdBatchCompletionStateResponse()
+ .setHandle(packet.getStatementHandle())
+ .setRecordsCount(messageCount)
+ .setUpdateCounts(new int[0]));
}
+ FirebirdServerPreparedStatement preparedStatement = connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(batchStatement.getStatementHandle());
+ FirebirdBatchedStatementsExecutor executor = new FirebirdBatchedStatementsExecutor(connectionSession, preparedStatement, batchStatement.getParameterValues(), batchStatement.isMultiError());
+ FirebirdBatchCompletion completion = executor.executeBatch();
batchStatement.reset();
- return Collections.singleton(new FirebirdBatchCompletionStateResponse()
+ return Collections.singleton(createResponse(completion, batchStatement.isRecordCounts()));
+ }
+
+ private void validateTransactionHandle() {
+ ShardingSpherePreconditions.checkState(FirebirdTransactionIdGenerator.getInstance().isTransactionActive(connectionSession.getConnectionId(), packet.getTransactionHandle()),
+ () -> new InvalidTransactionHandleException(packet.getTransactionHandle()));
+ }
+
+ private FirebirdBatchCompletionStateResponse createResponse(final FirebirdBatchCompletion completion, final boolean recordCounts) {
+ FirebirdBatchCompletionStateResponse result = new FirebirdBatchCompletionStateResponse()
.setHandle(packet.getStatementHandle())
- .setRecordsCount(updateCounts.length)
- .setUpdateCounts(batchStatement.isRecordCounts() ? updateCounts : new int[0]));
+ .setRecordsCount(completion.getRecordsCount())
+ .setUpdateCounts(recordCounts ? completion.getUpdateCounts() : new int[0]);
+ for (FirebirdBatchCompletion.Failure each : completion.getFailures()) {
+ result.addDetailedError(each.getMessageIndex(), new FirebirdStatusVector(each.getCause()));
+ }
+ return result;
}
}
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchMessageCommandExecutor.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchMessageCommandExecutor.java
index 3b5c8943ac0..b1108c4e50b 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchMessageCommandExecutor.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchMessageCommandExecutor.java
@@ -18,7 +18,8 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.batch;
import lombok.RequiredArgsConstructor;
-import org.apache.shardingsphere.database.protocol.firebird.exception.FirebirdProtocolException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.BatchTooBigException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchHandleException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchRegistry;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchMessageCommandPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchStatement;
@@ -47,11 +48,10 @@ public final class FirebirdBatchMessageCommandExecutor implements CommandExecuto
int connectionId = connectionSession.getConnectionId();
FirebirdBatchStatement batchStatement = FirebirdBatchRegistry.getInstance().getBatchStatement(connectionId, packet.getStatementHandle());
if (null == batchStatement) {
- throw new FirebirdProtocolException("Batch statement not found for connectionId: %d, statement handle: %d", connectionId, packet.getStatementHandle());
+ throw new InvalidBatchHandleException(packet.getStatementHandle());
}
if (batchStatement.getAccumulatedSize() + packet.getDataLength() > batchStatement.getBufferSize()) {
- throw new FirebirdProtocolException("Batch is too big: accumulated %d + %d bytes exceeds buffer size limit %d bytes",
- batchStatement.getAccumulatedSize(), packet.getDataLength(), batchStatement.getBufferSize());
+ throw new BatchTooBigException(packet.getStatementHandle(), batchStatement.getAccumulatedSize(), packet.getDataLength(), batchStatement.getBufferSize());
}
for (List<Object> each : packet.readParameterValues(batchStatement.getColumnDescriptors())) {
batchStatement.addParameterValues(each);
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchedStatementsExecutor.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchedStatementsExecutor.java
index d99f5f7626e..6294d12aab8 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchedStatementsExecutor.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchedStatementsExecutor.java
@@ -17,6 +17,7 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.batch;
+import lombok.RequiredArgsConstructor;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.infra.binder.context.aware.ParameterAware;
import org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
@@ -50,25 +51,34 @@ import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.FirebirdServerPreparedStatement;
import org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
+import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NavigableMap;
import java.util.Optional;
+import java.util.TreeMap;
/**
* Batched statements executor for Firebird.
*/
public final class FirebirdBatchedStatementsExecutor {
+ private static final int NOT_EXECUTED = Integer.MIN_VALUE;
+
private final KernelProcessor kernelProcessor = new KernelProcessor();
private final JDBCExecutor jdbcExecutor;
@@ -83,14 +93,20 @@ public final class FirebirdBatchedStatementsExecutor {
private final Map<ExecutionUnit, List<Integer>> executionUnitBatchMessageIndexes = new LinkedHashMap<>();
+ private final Map<PreparedStatement, List<List<Object>>> pendingBatchParams = new IdentityHashMap<>();
+
private final int batchMessageCount;
+ private final boolean multiError;
+
private final ExecutionContext anyExecutionContext;
private ExecutionGroupContext<JDBCExecutionUnit> executionGroupContext;
- public FirebirdBatchedStatementsExecutor(final ConnectionSession connectionSession, final FirebirdServerPreparedStatement preparedStatement, final List<List<Object>> parameterSets) {
+ public FirebirdBatchedStatementsExecutor(final ConnectionSession connectionSession, final FirebirdServerPreparedStatement preparedStatement, final List<List<Object>> parameterSets,
+ final boolean multiError) {
jdbcExecutor = new JDBCExecutor(BackendExecutorContext.getInstance().getExecutorEngine(), connectionSession.getConnectionContext());
+ this.multiError = multiError;
this.connectionSession = connectionSession;
metaDataContexts = ProxyContext.getInstance().getContextManager().getMetaDataContexts();
this.preparedStatement = preparedStatement;
@@ -161,72 +177,212 @@ public final class FirebirdBatchedStatementsExecutor {
/**
* Execute batch.
*
- * @return update counts
+ * @return batch completion in the Firebird domain
* @throws SQLException SQL exception
*/
- public int[] executeBatch() throws SQLException {
+ public FirebirdBatchCompletion executeBatch() throws SQLException {
connectionSession.getDatabaseConnectionManager().handleAutoCommit();
- addBatchedParametersToPreparedStatements();
- return executeBatchedPreparedStatements();
+ prepareBatchedStatements();
+ List<BatchExecution> batchExecutions;
+ List<BatchExecutionUnitResult> executeResults;
+ try {
+ batchExecutions = createBatchExecutionPlan();
+ executeResults = executeBatchedPreparedStatements(batchExecutions);
+ } finally {
+ closePreparedStatements();
+ }
+ return createBatchCompletion(batchExecutions, executeResults);
}
- private void addBatchedParametersToPreparedStatements() throws SQLException {
+ private void prepareBatchedStatements() throws SQLException {
Collection<ShardingSphereRule> rules = metaDataContexts.getMetaData().getDatabase(connectionSession.getUsedDatabaseName()).getRuleMetaData().getRules();
int maxConnectionsSizePerQuery = metaDataContexts.getMetaData().getProps().<Integer>getValue(ConfigurationPropertyKey.MAX_CONNECTIONS_SIZE_PER_QUERY);
DriverExecutionPrepareEngine<JDBCExecutionUnit, Connection> prepareEngine = new DriverExecutionPrepareEngine<>(JDBCDriverType.PREPARED_STATEMENT, maxConnectionsSizePerQuery,
connectionSession.getDatabaseConnectionManager(), (JDBCBackendStatement) connectionSession.getStatementManager(), new StatementOption(false), rules, metaDataContexts.getMetaData());
executionGroupContext = prepareEngine.prepare(connectionSession.getUsedDatabaseName(), anyExecutionContext, executionUnitParams.keySet(),
new ExecutionGroupReportContext(connectionSession.getProcessId(), connectionSession.getUsedDatabaseName(), connectionSession.getConnectionContext().getGrantee()));
- for (ExecutionGroup<JDBCExecutionUnit> eachGroup : executionGroupContext.getInputGroups()) {
- for (JDBCExecutionUnit each : eachGroup.getInputs()) {
- prepareJDBCExecutionUnit(each);
- }
- }
}
- private void prepareJDBCExecutionUnit(final JDBCExecutionUnit jdbcExecutionUnit) throws SQLException {
- PreparedStatement preparedStatement = (PreparedStatement) jdbcExecutionUnit.getStorageResource();
- for (List<Object> eachGroupParam : executionUnitParams.getOrDefault(jdbcExecutionUnit.getExecutionUnit(), Collections.emptyList())) {
- ListIterator<Object> params = eachGroupParam.listIterator();
- while (params.hasNext()) {
- int paramIndex = params.nextIndex() + 1;
- Object value = params.next();
+ private void addPendingBatch(final BatchExecution batchExecution) throws SQLException {
+ PreparedStatement preparedStatement = (PreparedStatement) batchExecution.executionUnit.getStorageResource();
+ List<List<Object>> params = getBatchParams(batchExecution);
+ pendingBatchParams.put(preparedStatement, params);
+ addBatchedParameters(preparedStatement, params);
+ }
+
+ private void addBatchedParameters(final PreparedStatement preparedStatement, final List<List<Object>> params) throws SQLException {
+ for (List<Object> eachGroupParam : params) {
+ ListIterator<Object> eachParams = eachGroupParam.listIterator();
+ while (eachParams.hasNext()) {
+ int paramIndex = eachParams.nextIndex() + 1;
+ Object value = eachParams.next();
preparedStatement.setObject(paramIndex, value);
}
preparedStatement.addBatch();
}
}
- private int[] executeBatchedPreparedStatements() throws SQLException {
+ private List<List<Object>> getBatchParams(final BatchExecution batchExecution) {
+ return executionUnitParams.getOrDefault(batchExecution.executionUnit.getExecutionUnit(), Collections.<List<Object>>emptyList())
+ .subList(batchExecution.fromOffset, batchExecution.toOffset);
+ }
+
+ private List<Integer> getBatchMessageIndexes(final BatchExecution batchExecution) {
+ return executionUnitBatchMessageIndexes.getOrDefault(batchExecution.executionUnit.getExecutionUnit(), Collections.<Integer>emptyList())
+ .subList(batchExecution.fromOffset, batchExecution.toOffset);
+ }
+
+ private void closePreparedStatements() throws SQLException {
+ for (ExecutionGroup<JDBCExecutionUnit> eachGroup : executionGroupContext.getInputGroups()) {
+ for (JDBCExecutionUnit each : eachGroup.getInputs()) {
+ each.getStorageResource().close();
+ }
+ }
+ }
+
+ private List<BatchExecutionUnitResult> executeBatchedPreparedStatements(final List<BatchExecution> batchExecutions) throws SQLException {
boolean isExceptionThrown = SQLExecutorExceptionHandler.isExceptionThrown();
ShardingSphereDatabase database = metaDataContexts.getMetaData().getDatabase(connectionSession.getUsedDatabaseName());
DatabaseType protocolType = database.getProtocolType();
- JDBCExecutorCallback<int[]> callback =
+ JDBCExecutorCallback<BatchExecutionUnitResult> callback =
new BatchedStatementsJDBCExecutorCallback(protocolType, database.getResourceMetaData(), preparedStatement.getSqlStatementContext().getSqlStatement(), isExceptionThrown);
- List<int[]> executeResults = jdbcExecutor.execute(executionGroupContext, callback);
- return getBatchMessageUpdateCounts(executeResults);
+ return isHaltedOnFirstFailure() ? executeInBatchMessageOrder(batchExecutions, callback) : jdbcExecutor.execute(executionGroupContext, callback);
+ }
+
+ private List<BatchExecutionUnitResult> executeInBatchMessageOrder(final List<BatchExecution> batchExecutions,
+ final JDBCExecutorCallback<BatchExecutionUnitResult> callback) throws SQLException {
+ List<BatchExecutionUnitResult> result = new LinkedList<>();
+ for (BatchExecution each : batchExecutions) {
+ addPendingBatch(each);
+ List<BatchExecutionUnitResult> executeResults = jdbcExecutor.execute(createSingleUnitExecutionGroupContext(each.executionUnit), callback);
+ result.addAll(executeResults);
+ if (executeResults.stream().anyMatch(eachResult -> !eachResult.failures.isEmpty())) {
+ break;
+ }
+ }
+ return result;
+ }
+
+ private ExecutionGroupContext<JDBCExecutionUnit> createSingleUnitExecutionGroupContext(final JDBCExecutionUnit executionUnit) {
+ return new ExecutionGroupContext<>(Collections.singleton(new ExecutionGroup<>(Collections.singletonList(executionUnit))), executionGroupContext.getReportContext());
+ }
+
+ private FirebirdBatchCompletion createBatchCompletion(final List<BatchExecution> batchExecutions, final List<BatchExecutionUnitResult> executeResults) throws SQLException {
+ int[] messageUpdateCounts = new int[batchMessageCount];
+ Arrays.fill(messageUpdateCounts, NOT_EXECUTED);
+ NavigableMap<Integer, SQLException> failedMessages = new TreeMap<>();
+ Iterator<BatchExecution> executions = batchExecutions.iterator();
+ Iterator<BatchExecutionUnitResult> results = executeResults.iterator();
+ while (executions.hasNext() && results.hasNext()) {
+ List<Integer> batchMessageIndexes = getBatchMessageIndexes(executions.next());
+ BatchExecutionUnitResult eachResult = results.next();
+ mergeUpdateCounts(messageUpdateCounts, batchMessageIndexes, eachResult.updateCounts);
+ markFailedMessages(messageUpdateCounts, failedMessages, batchMessageIndexes, eachResult.failures);
+ }
+ int processedMessageCount = getProcessedMessageCount(messageUpdateCounts, failedMessages);
+ return new FirebirdBatchCompletion(processedMessageCount, toFirebirdUpdateCounts(messageUpdateCounts, processedMessageCount), createFailures(failedMessages, processedMessageCount));
+ }
+
+ private void markFailedMessages(final int[] messageUpdateCounts, final Map<Integer, SQLException> failedMessages,
+ final List<Integer> batchMessageIndexes, final Map<Integer, SQLException> failedOffsets) {
+ for (Entry<Integer, SQLException> each : failedOffsets.entrySet()) {
+ markFailedMessage(messageUpdateCounts, failedMessages, getBatchMessageIndex(batchMessageIndexes, each.getKey()), each.getValue());
+ }
+ }
+
+ private int getBatchMessageIndex(final List<Integer> batchMessageIndexes, final int offset) {
+ if (batchMessageIndexes.isEmpty()) {
+ return 0;
+ }
+ return offset < batchMessageIndexes.size() ? batchMessageIndexes.get(offset) : batchMessageIndexes.get(batchMessageIndexes.size() - 1);
+ }
+
+ private void markFailedMessage(final int[] messageUpdateCounts, final Map<Integer, SQLException> failedMessages, final int messageIndex, final SQLException failureCause) {
+ messageUpdateCounts[messageIndex] = Statement.EXECUTE_FAILED;
+ failedMessages.putIfAbsent(messageIndex, failureCause);
+ }
+
+ private int getProcessedMessageCount(final int[] messageUpdateCounts, final NavigableMap<Integer, SQLException> failedMessages) throws SQLException {
+ int notExecutedMessageIndex = batchMessageCount;
+ for (int i = 0; i < batchMessageCount; i++) {
+ if (NOT_EXECUTED == messageUpdateCounts[i]) {
+ notExecutedMessageIndex = i;
+ break;
+ }
+ }
+ if (failedMessages.isEmpty()) {
+ return notExecutedMessageIndex;
+ }
+ Entry<Integer, SQLException> firstFailedMessage = failedMessages.firstEntry();
+ if (notExecutedMessageIndex <= firstFailedMessage.getKey()) {
+ throw firstFailedMessage.getValue();
+ }
+ return multiError ? notExecutedMessageIndex : Math.min(notExecutedMessageIndex, firstFailedMessage.getKey() + 1);
+ }
+
+ private Collection<FirebirdBatchCompletion.Failure> createFailures(final Map<Integer, SQLException> failedMessages, final int processedMessageCount) {
+ Collection<FirebirdBatchCompletion.Failure> result = new LinkedList<>();
+ for (Entry<Integer, SQLException> each : failedMessages.entrySet()) {
+ if (each.getKey() < processedMessageCount) {
+ result.add(new FirebirdBatchCompletion.Failure(each.getKey(), each.getValue()));
+ }
+ }
+ return result;
}
- private int[] getBatchMessageUpdateCounts(final List<int[]> executeResults) {
- int[] result = new int[batchMessageCount];
- Iterator<ExecutionUnit> executionUnits = getExecutionUnitsInExecutionOrder().iterator();
- Iterator<int[]> results = executeResults.iterator();
- while (executionUnits.hasNext() && results.hasNext()) {
- mergeUpdateCounts(result, executionUnitBatchMessageIndexes.getOrDefault(executionUnits.next(), Collections.emptyList()), results.next());
+ private int[] toFirebirdUpdateCounts(final int[] messageUpdateCounts, final int processedMessageCount) {
+ int[] result = Arrays.copyOf(messageUpdateCounts, processedMessageCount);
+ for (int i = 0; i < result.length; i++) {
+ if (Statement.EXECUTE_FAILED == result[i]) {
+ result[i] = FirebirdBatchCompletion.EXECUTE_FAILED;
+ }
}
return result;
}
- private List<ExecutionUnit> getExecutionUnitsInExecutionOrder() {
- List<ExecutionUnit> result = new LinkedList<>();
+ private List<BatchExecution> createBatchExecutionPlan() throws SQLException {
+ List<JDBCExecutionUnit> executionUnits = new LinkedList<>();
for (ExecutionGroup<JDBCExecutionUnit> eachGroup : executionGroupContext.getInputGroups()) {
- for (JDBCExecutionUnit each : eachGroup.getInputs()) {
- result.add(each.getExecutionUnit());
+ executionUnits.addAll(eachGroup.getInputs());
+ }
+ if (!isHaltedOnFirstFailure()) {
+ return createWholeUnitBatchExecutions(executionUnits);
+ }
+ return createBatchExecutionsInMessageOrder(executionUnits);
+ }
+
+ private List<BatchExecution> createWholeUnitBatchExecutions(final Collection<JDBCExecutionUnit> executionUnits) throws SQLException {
+ List<BatchExecution> result = new ArrayList<>(executionUnits.size());
+ for (JDBCExecutionUnit each : executionUnits) {
+ BatchExecution batchExecution = new BatchExecution(each, 0, executionUnitParams.getOrDefault(each.getExecutionUnit(), Collections.emptyList()).size());
+ addPendingBatch(batchExecution);
+ result.add(batchExecution);
+ }
+ return result;
+ }
+
+ private List<BatchExecution> createBatchExecutionsInMessageOrder(final Collection<JDBCExecutionUnit> executionUnits) {
+ Map<Integer, Collection<JDBCExecutionUnit>> unitsByBatchMessageIndex = new TreeMap<>();
+ for (JDBCExecutionUnit each : executionUnits) {
+ for (int eachMessageIndex : executionUnitBatchMessageIndexes.getOrDefault(each.getExecutionUnit(), Collections.<Integer>emptyList())) {
+ unitsByBatchMessageIndex.computeIfAbsent(eachMessageIndex, key -> new LinkedList<>()).add(each);
+ }
+ }
+ List<BatchExecution> result = new ArrayList<>(batchMessageCount);
+ Map<JDBCExecutionUnit, Integer> consumedOffsets = new IdentityHashMap<>();
+ for (Entry<Integer, Collection<JDBCExecutionUnit>> entry : unitsByBatchMessageIndex.entrySet()) {
+ for (JDBCExecutionUnit each : entry.getValue()) {
+ int offset = consumedOffsets.merge(each, 1, Integer::sum) - 1;
+ result.add(new BatchExecution(each, offset, offset + 1));
}
}
return result;
}
+ private boolean isHaltedOnFirstFailure() {
+ return !multiError;
+ }
+
private void mergeUpdateCounts(final int[] result, final List<Integer> batchMessageIndexes, final int[] updateCounts) {
int countSize = Math.min(batchMessageIndexes.size(), updateCounts.length);
for (int i = 0; i < countSize; i++) {
@@ -237,6 +393,9 @@ public final class FirebirdBatchedStatementsExecutor {
}
private int mergeUpdateCount(final int current, final int updateCount) {
+ if (NOT_EXECUTED == current) {
+ return updateCount;
+ }
if (Statement.EXECUTE_FAILED == updateCount || Statement.EXECUTE_FAILED == current) {
return Statement.EXECUTE_FAILED;
}
@@ -246,7 +405,7 @@ public final class FirebirdBatchedStatementsExecutor {
return current + updateCount;
}
- private static final class BatchedStatementsJDBCExecutorCallback extends JDBCExecutorCallback<int[]> {
+ private final class BatchedStatementsJDBCExecutorCallback extends JDBCExecutorCallback<BatchExecutionUnitResult> {
private BatchedStatementsJDBCExecutorCallback(final DatabaseType protocolType, final ResourceMetaData resourceMetaData, final SQLStatement sqlStatement,
final boolean isExceptionThrown) {
@@ -254,18 +413,107 @@ public final class FirebirdBatchedStatementsExecutor {
}
@Override
- protected int[] executeSQL(final String sql, final Statement statement, final ConnectionMode connectionMode, final DatabaseType storageType) throws SQLException {
- try {
- return statement.executeBatch();
- } finally {
- statement.close();
+ protected BatchExecutionUnitResult executeSQL(final String sql, final Statement statement, final ConnectionMode connectionMode, final DatabaseType storageType) throws SQLException {
+ return executeBatchedStatement((PreparedStatement) statement);
+ }
+
+ private BatchExecutionUnitResult executeBatchedStatement(final PreparedStatement statement) throws SQLException {
+ List<List<Object>> params = pendingBatchParams.getOrDefault(statement, Collections.emptyList());
+ List<Integer> updateCounts = new LinkedList<>();
+ Map<Integer, SQLException> failures = new TreeMap<>();
+ while (true) {
+ int executedCount = updateCounts.size();
+ try {
+ addAll(updateCounts, statement.executeBatch());
+ return createResult(updateCounts, failures);
+ } catch (final SQLException ex) {
+ int[] executedUpdateCounts = getExecutedUpdateCounts(ex);
+ addAll(updateCounts, executedUpdateCounts);
+ registerFailures(failures, ex, updateCounts, executedCount, params.size() - executedCount > executedUpdateCounts.length);
+ }
+ if (!multiError || updateCounts.size() >= params.size()) {
+ return createResult(updateCounts, failures);
+ }
+ statement.clearBatch();
+ addBatchedParameters(statement, params.subList(updateCounts.size(), params.size()));
+ }
+ }
+
+ private int[] getExecutedUpdateCounts(final SQLException ex) {
+ if (!(ex instanceof BatchUpdateException)) {
+ return new int[0];
}
+ int[] result = ((BatchUpdateException) ex).getUpdateCounts();
+ return null == result ? new int[0] : result;
+ }
+
+ private void registerFailures(final Map<Integer, SQLException> failures, final SQLException ex, final List<Integer> updateCounts,
+ final int executedCount, final boolean isHaltedBeforeRemainingMessages) {
+ List<Integer> failedOffsets = new LinkedList<>();
+ for (int i = executedCount; i < updateCounts.size(); i++) {
+ if (Statement.EXECUTE_FAILED == updateCounts.get(i)) {
+ failedOffsets.add(i);
+ }
+ }
+ if (isHaltedBeforeRemainingMessages) {
+ failedOffsets.add(updateCounts.size());
+ updateCounts.add(Statement.EXECUTE_FAILED);
+ }
+ if (failedOffsets.isEmpty()) {
+ failedOffsets.add(Math.max(executedCount, updateCounts.size() - 1));
+ }
+ Iterator<SQLException> causes = getFailureCauses(ex, failedOffsets.size()).iterator();
+ for (int each : failedOffsets) {
+ failures.putIfAbsent(each, causes.next());
+ }
+ }
+
+ private Collection<SQLException> getFailureCauses(final SQLException ex, final int failedCount) {
+ Collection<SQLException> chainedCauses = new LinkedList<>();
+ for (SQLException each = ex.getNextException(); null != each; each = each.getNextException()) {
+ chainedCauses.add(each);
+ }
+ if (chainedCauses.size() == failedCount) {
+ return chainedCauses;
+ }
+ return Collections.nCopies(failedCount, ex);
+ }
+
+ private void addAll(final List<Integer> updateCounts, final int[] executedUpdateCounts) {
+ for (int each : executedUpdateCounts) {
+ updateCounts.add(each);
+ }
+ }
+
+ private BatchExecutionUnitResult createResult(final List<Integer> updateCounts, final Map<Integer, SQLException> failures) {
+ int[] result = new int[updateCounts.size()];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = updateCounts.get(i);
+ }
+ return new BatchExecutionUnitResult(result, failures);
}
- @SuppressWarnings("OptionalContainsCollection")
@Override
- protected Optional<int[]> getSaneResult(final SQLStatement sqlStatement, final SQLException ex) {
+ protected Optional<BatchExecutionUnitResult> getSaneResult(final SQLStatement sqlStatement, final SQLException ex) {
return Optional.empty();
}
}
+
+ @RequiredArgsConstructor
+ private static final class BatchExecution {
+
+ private final JDBCExecutionUnit executionUnit;
+
+ private final int fromOffset;
+
+ private final int toOffset;
+ }
+
+ @RequiredArgsConstructor
+ private static final class BatchExecutionUnitResult {
+
+ private final int[] updateCounts;
+
+ private final Map<Integer, SQLException> failures;
+ }
}
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/execute/FirebirdExecuteStatementCommandExecutor.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/execute/FirebirdExecuteStatementCommandExecutor.java
index 176a0b3128e..fdb0db60819 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/execute/FirebirdExecuteStatementCommandExecutor.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/execute/FirebirdExecuteStatementCommandExecutor.java
@@ -20,6 +20,8 @@ package org.apache.shardingsphere.proxy.frontend.firebird.command.query.statemen
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidStatementHandleException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidTransactionHandleException;
import org.apache.shardingsphere.database.protocol.binary.BinaryCell;
import org.apache.shardingsphere.database.protocol.binary.BinaryRow;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.FirebirdBinaryColumnType;
@@ -30,6 +32,7 @@ import org.apache.shardingsphere.database.protocol.firebird.packet.generic.Fireb
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
import org.apache.shardingsphere.infra.binder.context.aware.ParameterAware;
import org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
+import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
import org.apache.shardingsphere.infra.session.query.QueryContext;
import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import org.apache.shardingsphere.proxy.backend.context.ProxyContext;
@@ -47,6 +50,7 @@ import org.apache.shardingsphere.proxy.frontend.firebird.command.query.FirebirdS
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.blob.cache.FirebirdBlobWriteCache;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement.FirebirdStatementResourceCleaner;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement.fetch.FirebirdFetchStatementCache;
+import org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction.FirebirdTransactionIdGenerator;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -75,6 +79,10 @@ public final class FirebirdExecuteStatementCommandExecutor implements CommandExe
connectionSession.beginPreparedStatementCache(FirebirdStatementResourceCleaner.createPreparedStatementCacheKey(packet.getStatementId()));
try {
FirebirdServerPreparedStatement preparedStatement = connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(packet.getStatementId());
+ if (null == preparedStatement) {
+ throw new InvalidStatementHandleException(packet.getStatementId());
+ }
+ validateTransactionHandle();
ResponseHeader responseHeader = executePreparedStatement(preparedStatement, packet.getParameterValues());
if (responseHeader instanceof QueryResponseHeader) {
responseType = ResponseType.QUERY;
@@ -95,6 +103,11 @@ public final class FirebirdExecuteStatementCommandExecutor implements CommandExe
}
}
+ private void validateTransactionHandle() {
+ ShardingSpherePreconditions.checkState(FirebirdTransactionIdGenerator.getInstance().isTransactionActive(connectionSession.getConnectionId(), packet.getTransactionId()),
+ () -> new InvalidTransactionHandleException(packet.getTransactionId()));
+ }
+
private ResponseHeader executePreparedStatement(final FirebirdServerPreparedStatement preparedStatement, final List<Object> params) throws SQLException {
List<Long> blobIdsToRemove = bindBlobParameters(params);
try {
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutor.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutor.java
index 6040f66c2c2..f5eec4e6bad 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutor.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutor.java
@@ -22,6 +22,7 @@ import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
import org.apache.shardingsphere.database.connector.firebird.metadata.data.FirebirdBlobInfoRegistry;
import org.apache.shardingsphere.database.connector.firebird.metadata.data.FirebirdNonFixedLengthColumnSizeRegistry;
+import org.apache.shardingsphere.database.exception.core.exception.syntax.database.NoDatabaseSelectedException;
import org.apache.shardingsphere.database.protocol.firebird.exception.FirebirdProtocolException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.info.type.sql.FirebirdSQLInfoPacketType;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.info.type.sql.FirebirdSQLInfoReturnValue;
@@ -46,6 +47,7 @@ import org.apache.shardingsphere.infra.binder.context.statement.type.dml.InsertS
import org.apache.shardingsphere.infra.binder.context.statement.type.dml.SelectStatementContext;
import org.apache.shardingsphere.infra.binder.context.statement.type.dml.UpdateStatementContext;
import org.apache.shardingsphere.infra.binder.engine.SQLBindEngine;
+import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereColumn;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereSchema;
import org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereTable;
@@ -75,6 +77,7 @@ import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table
import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.TableSegment;
import org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.DDLStatement;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.database.DropDatabaseStatement;
import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.DeleteStatement;
import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.InsertStatement;
import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.SelectStatement;
@@ -111,7 +114,7 @@ public final class FirebirdPrepareStatementCommandExecutor implements CommandExe
MetaDataContexts metaDataContexts = ProxyContext.getInstance().getContextManager().getMetaDataContexts();
SQLParserRule sqlParserRule = metaDataContexts.getMetaData().getGlobalRuleMetaData().getSingleRule(SQLParserRule.class);
DatabaseType databaseType = TypedSPILoader.getService(DatabaseType.class, "Firebird");
- SQLStatement sqlStatement = sqlParserRule.getSQLParserEngine(databaseType).parse(packet.getSQL(), true);
+ SQLStatement sqlStatement = resolveCurrentDatabase(sqlParserRule.getSQLParserEngine(databaseType).parse(packet.getSQL(), true));
SQLStatementContext sqlStatementContext = new SQLBindEngine(
metaDataContexts.getMetaData(), connectionSession.getCurrentDatabaseName(), packet.getHintValueContext()).bind(sqlStatement);
int statementId = getStatementId();
@@ -123,6 +126,15 @@ public final class FirebirdPrepareStatementCommandExecutor implements CommandExe
return createResponse(sqlStatementContext, metaDataContexts);
}
+ private SQLStatement resolveCurrentDatabase(final SQLStatement sqlStatement) {
+ if (!(sqlStatement instanceof DropDatabaseStatement) || null != ((DropDatabaseStatement) sqlStatement).getDatabaseName()) {
+ return sqlStatement;
+ }
+ String currentDatabaseName = connectionSession.getUsedDatabaseName();
+ ShardingSpherePreconditions.checkNotNull(currentDatabaseName, NoDatabaseSelectedException::new);
+ return new DropDatabaseStatement(sqlStatement.getDatabaseType(), currentDatabaseName, ((DropDatabaseStatement) sqlStatement).isIfExists());
+ }
+
private int getStatementId() {
return packet.isValidStatementHandle() ? packet.getStatementId() : FirebirdStatementIdGenerator.getInstance().getStatementId(connectionSession.getConnectionId());
}
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdCommitTransactionCommandExecutor.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdCommitTransactionCommandExecutor.java
index 131047a17fc..69f492b8c67 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdCommitTransactionCommandExecutor.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdCommitTransactionCommandExecutor.java
@@ -18,6 +18,7 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction;
import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidTransactionHandleException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.transaction.FirebirdCommitTransactionPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
@@ -41,11 +42,20 @@ public final class FirebirdCommitTransactionCommandExecutor implements CommandEx
@Override
public Collection<DatabasePacket> execute() throws SQLException {
+ int transactionHandle = packet.getTransactionId();
+ validateTransactionHandle(transactionHandle);
if (!connectionSession.isAutoCommit()) {
ProxyBackendTransactionManager transactionManager = new ProxyBackendTransactionManager(connectionSession.getDatabaseConnectionManager());
// TODO add rollback and return exception
transactionManager.commit();
}
+ FirebirdTransactionIdGenerator.getInstance().closeTransaction(connectionSession.getConnectionId(), transactionHandle);
return Collections.singleton(new FirebirdGenericResponsePacket());
}
+
+ private void validateTransactionHandle(final int transactionHandle) {
+ if (!FirebirdTransactionIdGenerator.getInstance().isTransactionActive(connectionSession.getConnectionId(), transactionHandle)) {
+ throw new InvalidTransactionHandleException(transactionHandle);
+ }
+ }
}
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdRollbackTransactionCommandExecutor.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdRollbackTransactionCommandExecutor.java
index ba843aa77f8..d5dca1127d2 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdRollbackTransactionCommandExecutor.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdRollbackTransactionCommandExecutor.java
@@ -18,6 +18,7 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction;
import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidTransactionHandleException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.transaction.FirebirdRollbackTransactionPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
@@ -41,10 +42,19 @@ public final class FirebirdRollbackTransactionCommandExecutor implements Command
@Override
public Collection<DatabasePacket> execute() throws SQLException {
+ int transactionHandle = packet.getTransactionId();
+ validateTransactionHandle(transactionHandle);
if (!connectionSession.isAutoCommit()) {
ProxyBackendTransactionManager transactionManager = new ProxyBackendTransactionManager(connectionSession.getDatabaseConnectionManager());
transactionManager.rollback();
}
+ FirebirdTransactionIdGenerator.getInstance().closeTransaction(connectionSession.getConnectionId(), transactionHandle);
return Collections.singleton(new FirebirdGenericResponsePacket());
}
+
+ private void validateTransactionHandle(final int transactionHandle) {
+ if (!FirebirdTransactionIdGenerator.getInstance().isTransactionActive(connectionSession.getConnectionId(), transactionHandle)) {
+ throw new InvalidTransactionHandleException(transactionHandle);
+ }
+ }
}
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdStartTransactionCommandExecutor.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdStartTransactionCommandExecutor.java
index 9f82a6d281a..828cef82b09 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdStartTransactionCommandExecutor.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdStartTransactionCommandExecutor.java
@@ -18,6 +18,7 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction;
import lombok.RequiredArgsConstructor;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.ExcessTransactionsException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.transaction.FirebirdStartTransactionPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
@@ -33,16 +34,25 @@ import java.util.Collections;
@RequiredArgsConstructor
public final class FirebirdStartTransactionCommandExecutor implements CommandExecutor {
+ private static final int MAX_CONCURRENT_TRANSACTIONS = 1;
+
private final FirebirdStartTransactionPacket packet;
private final ConnectionSession connectionSession;
@Override
public Collection<DatabasePacket> execute() {
+ validateNoActiveTransaction();
connectionSession.setAutoCommit(packet.isAutoCommit());
connectionSession.setReadOnly(packet.isReadOnly());
connectionSession.setIsolationLevel(packet.getIsolationLevel());
int transactionId = FirebirdTransactionIdGenerator.getInstance().nextTransactionId(connectionSession.getConnectionId());
return Collections.singleton(new FirebirdGenericResponsePacket().setHandle(transactionId));
}
+
+ private void validateNoActiveTransaction() {
+ if (FirebirdTransactionIdGenerator.getInstance().hasActiveTransaction(connectionSession.getConnectionId())) {
+ throw new ExcessTransactionsException(MAX_CONCURRENT_TRANSACTIONS);
+ }
+ }
}
diff --git a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdTransactionIdGenerator.java b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdTransactionIdGenerator.java
index 57c8303a0f6..a991181cbcb 100644
--- a/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdTransactionIdGenerator.java
+++ b/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdTransactionIdGenerator.java
@@ -21,6 +21,7 @@ import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@@ -34,6 +35,8 @@ public final class FirebirdTransactionIdGenerator {
private final Map<Integer, AtomicInteger> connectionRegistry = new ConcurrentHashMap<>();
+ private final Map<Integer, Set<Integer>> activeTransactionRegistry = new ConcurrentHashMap<>();
+
/**
* Get prepared transaction registry instance.
*
@@ -50,6 +53,7 @@ public final class FirebirdTransactionIdGenerator {
*/
public void registerConnection(final int connectionId) {
connectionRegistry.put(connectionId, new AtomicInteger());
+ activeTransactionRegistry.put(connectionId, ConcurrentHashMap.newKeySet());
}
/**
@@ -59,7 +63,40 @@ public final class FirebirdTransactionIdGenerator {
* @return generated transaction ID
*/
public int nextTransactionId(final int connectionId) {
- return getTransactionCounter(connectionId).incrementAndGet();
+ int result = getTransactionCounter(connectionId).incrementAndGet();
+ getActiveTransactions(connectionId).add(result);
+ return result;
+ }
+
+ /**
+ * Judge whether transaction is active for connection.
+ *
+ * @param connectionId connection ID
+ * @param transactionId transaction ID
+ * @return is transaction active or not
+ */
+ public boolean isTransactionActive(final int connectionId, final int transactionId) {
+ return getActiveTransactions(connectionId).contains(transactionId);
+ }
+
+ /**
+ * Judge whether any transaction is active for connection.
+ *
+ * @param connectionId connection ID
+ * @return has active transaction or not
+ */
+ public boolean hasActiveTransaction(final int connectionId) {
+ return !getActiveTransactions(connectionId).isEmpty();
+ }
+
+ /**
+ * Close transaction for connection.
+ *
+ * @param connectionId connection ID
+ * @param transactionId transaction ID
+ */
+ public void closeTransaction(final int connectionId, final int transactionId) {
+ getActiveTransactions(connectionId).remove(transactionId);
}
/**
@@ -79,6 +116,7 @@ public final class FirebirdTransactionIdGenerator {
*/
public void unregisterConnection(final int connectionId) {
connectionRegistry.remove(connectionId);
+ activeTransactionRegistry.remove(connectionId);
}
private AtomicInteger getTransactionCounter(final int connectionId) {
@@ -89,4 +127,12 @@ public final class FirebirdTransactionIdGenerator {
return result;
}
+ private Set<Integer> getActiveTransactions(final int connectionId) {
+ Set<Integer> result = activeTransactionRegistry.get(connectionId);
+ if (null == result) {
+ throw new IllegalStateException("No transaction ID generator found for connectionId: " + connectionId);
+ }
+ return result;
+ }
+
}
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngineTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngineTest.java
index 4ee71c64b38..56f8c8397e9 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngineTest.java
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/authentication/FirebirdAuthenticationEngineTest.java
@@ -25,6 +25,7 @@ import org.apache.shardingsphere.authentication.result.AuthenticationResult;
import org.apache.shardingsphere.authentication.result.AuthenticationResultBuilder;
import org.apache.shardingsphere.authority.rule.AuthorityRule;
import org.apache.shardingsphere.database.exception.core.exception.connection.AccessDeniedException;
+import org.apache.shardingsphere.database.exception.core.exception.data.InvalidParameterValueException;
import org.apache.shardingsphere.database.exception.core.exception.syntax.database.UnknownDatabaseException;
import org.apache.shardingsphere.database.protocol.constant.CommonConstants;
import org.apache.shardingsphere.database.protocol.firebird.constant.FirebirdAuthenticationMethod;
@@ -79,6 +80,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
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.ArgumentMatchers.anyString;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
@@ -86,6 +88,7 @@ import static org.mockito.Mockito.isA;
import static org.mockito.Mockito.lenient;
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;
@@ -268,6 +271,50 @@ class FirebirdAuthenticationEngineTest {
}
}
+ @SuppressWarnings("rawtypes")
+ @SneakyThrows(ReflectiveOperationException.class)
+ @Test
+ void assertAuthenticateAttachWithUnknownCharset() {
+ AuthorityRule rule = mock(AuthorityRule.class);
+ mockProxyContext(rule, true);
+ Attribute<Charset> charsetAttr = mock(Attribute.class);
+ when(context.channel().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY)).thenReturn(charsetAttr);
+ Plugins.getMemberAccessor().set(FirebirdAuthenticationEngine.class.getDeclaredField("currentAuthResult"), authenticationEngine,
+ AuthenticationResultBuilder.continued("root", "", "db", Collections.emptyMap()));
+ FirebirdPacketPayload payload = mockFirebirdPayload(FirebirdCommandPacketType.ATTACH);
+ try (
+ MockedConstruction<FirebirdAttachPacket> ignored = mockConstruction(FirebirdAttachPacket.class,
+ (attachPacket, construction) -> when(attachPacket.getEncoding()).thenReturn("FOO_CHARSET"))) {
+ assertThrows(InvalidParameterValueException.class, () -> authenticationEngine.authenticate(context, payload));
+ }
+ verify(charsetAttr, never()).set(any());
+ }
+
+ @SuppressWarnings("rawtypes")
+ @SneakyThrows(ReflectiveOperationException.class)
+ @Test
+ void assertAuthenticateAttachWithoutEncoding() {
+ AuthorityRule rule = mock(AuthorityRule.class);
+ ShardingSphereUser user = new ShardingSphereUser("root", "pwd", "");
+ when(rule.findUser(any(Grantee.class))).thenReturn(Optional.of(user));
+ mockProxyContext(rule, true);
+ Attribute<Charset> charsetAttr = mock(Attribute.class);
+ when(context.channel().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY)).thenReturn(charsetAttr);
+ Plugins.getMemberAccessor().set(FirebirdAuthenticationEngine.class.getDeclaredField("currentAuthResult"), authenticationEngine,
+ AuthenticationResultBuilder.continued("root", "", "db", Collections.emptyMap()));
+ FirebirdPacketPayload payload = mockFirebirdPayload(FirebirdCommandPacketType.ATTACH);
+ FirebirdAuthenticator authenticator = mock(FirebirdAuthenticator.class);
+ when(authenticator.authenticate(any(), any())).thenReturn(true);
+ try (
+ MockedConstruction<FirebirdAttachPacket> ignored = mockConstruction(FirebirdAttachPacket.class, (attachPacket, construction) -> when(attachPacket.getEncoding()).thenReturn(null));
+ MockedConstruction<AuthenticatorFactory> ignoredFactory = mockConstruction(AuthenticatorFactory.class,
+ (factory, construction) -> when(factory.newInstance(user)).thenReturn(authenticator))) {
+ AuthenticationResult actual = authenticationEngine.authenticate(context, payload);
+ assertTrue(actual.isFinished());
+ }
+ verify(charsetAttr).set(FirebirdCharacterSets.findCharacterSet("NONE"));
+ }
+
@Test
void assertAuthenticateConnectWithUnknownUser() {
AuthorityRule rule = mock(AuthorityRule.class);
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/FirebirdCommandExecuteEngineTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/FirebirdCommandExecuteEngineTest.java
index 1553c5775d9..c30659830c4 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/FirebirdCommandExecuteEngineTest.java
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/FirebirdCommandExecuteEngineTest.java
@@ -22,6 +22,7 @@ import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import org.apache.shardingsphere.database.protocol.firebird.constant.protocol.FirebirdConnectionProtocolVersion;
import org.apache.shardingsphere.database.protocol.firebird.constant.protocol.FirebirdProtocolVersion;
+import org.apache.shardingsphere.database.protocol.firebird.err.FirebirdErrorPacketFactory;
import org.apache.shardingsphere.database.protocol.firebird.packet.FirebirdPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.FirebirdCommandPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.FirebirdCommandPacketFactory;
@@ -46,7 +47,6 @@ import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
import org.apache.shardingsphere.proxy.frontend.command.executor.CommandExecutor;
import org.apache.shardingsphere.proxy.frontend.command.executor.QueryCommandExecutor;
import org.apache.shardingsphere.proxy.frontend.command.executor.ResponseType;
-import org.apache.shardingsphere.proxy.frontend.firebird.err.FirebirdErrorPacketFactory;
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;
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCreateCommandExecutorTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCreateCommandExecutorTest.java
index a2c2b64adb4..6d6d7b611e1 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCreateCommandExecutorTest.java
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCreateCommandExecutorTest.java
@@ -19,6 +19,12 @@ package org.apache.shardingsphere.proxy.frontend.firebird.command.query.batch;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.BatchAlreadyOpenedException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.BatchParametersRequiredException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchMessageFormatException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchParameterVersionException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidStatementHandleException;
+import org.apache.shardingsphere.database.protocol.firebird.err.FirebirdErrorPacketFactory;
import org.apache.shardingsphere.database.protocol.firebird.exception.FirebirdProtocolException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.FirebirdBinaryColumnType;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchCreateCommandPacket;
@@ -28,7 +34,6 @@ import org.apache.shardingsphere.database.protocol.firebird.packet.generic.Fireb
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.FirebirdServerPreparedStatement;
-import org.apache.shardingsphere.proxy.frontend.firebird.err.FirebirdErrorPacketFactory;
import org.firebirdsql.gds.BlrConstants;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@@ -66,6 +71,8 @@ class FirebirdBatchCreateCommandExecutorTest {
private static final int BATCH_VERSION_1 = 1;
+ private static final int TAG_MULTIERROR = 1;
+
private static final int TAG_RECORD_COUNTS = 2;
private static final int TAG_BUFFER_BYTES_SIZE = 3;
@@ -106,6 +113,7 @@ class FirebirdBatchCreateCommandExecutorTest {
assertNotNull(actualBatchStatement);
assertThat(actualBatchStatement.getStatementHandle(), is(STATEMENT_ID));
assertFalse(actualBatchStatement.isRecordCounts());
+ assertFalse(actualBatchStatement.isMultiError());
assertThat(actualBatchStatement.getColumnDescriptors().size(), is(1));
assertThat(actualBatchStatement.getColumnDescriptors().get(0).getType(), is(FirebirdBinaryColumnType.LEGACY_TEXT));
assertThat(actualBatchStatement.getColumnDescriptors().get(0).getLength(), is(4));
@@ -126,6 +134,19 @@ class FirebirdBatchCreateCommandExecutorTest {
assertTrue(FirebirdBatchRegistry.getInstance().getBatchStatement(CONNECTION_ID, STATEMENT_ID).isRecordCounts());
}
+ @Test
+ void assertExecuteWithMultiError() throws SQLException {
+ FirebirdBatchRegistry.getInstance().registerConnection(CONNECTION_ID);
+ when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
+ when(packet.getStatementHandle()).thenReturn(STATEMENT_ID);
+ when(connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(STATEMENT_ID)).thenReturn(preparedStatement);
+ when(packet.getBatchBlr()).thenReturn(createBatchBlr());
+ when(packet.getBatchMessageLength()).thenReturn(6L);
+ when(packet.getBatchParametersBuffer()).thenReturn(createBatchParametersBuffer(TAG_MULTIERROR, 1));
+ new FirebirdBatchCreateCommandExecutor(packet, connectionSession).execute();
+ assertTrue(FirebirdBatchRegistry.getInstance().getBatchStatement(CONNECTION_ID, STATEMENT_ID).isMultiError());
+ }
+
@Test
void assertExecuteWhenBatchAlreadyOpen() {
FirebirdBatchRegistry.getInstance().registerConnection(CONNECTION_ID);
@@ -134,7 +155,7 @@ class FirebirdBatchCreateCommandExecutorTest {
when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
when(packet.getStatementHandle()).thenReturn(STATEMENT_ID);
when(connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(STATEMENT_ID)).thenReturn(preparedStatement);
- assertThrows(FirebirdProtocolException.class, () -> new FirebirdBatchCreateCommandExecutor(packet, connectionSession).execute());
+ assertThrows(BatchAlreadyOpenedException.class, () -> new FirebirdBatchCreateCommandExecutor(packet, connectionSession).execute());
assertThat(FirebirdBatchRegistry.getInstance().getBatchStatement(CONNECTION_ID, STATEMENT_ID), is(expectedBatchStatement));
}
@@ -144,7 +165,7 @@ class FirebirdBatchCreateCommandExecutorTest {
when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
when(packet.getStatementHandle()).thenReturn(STATEMENT_ID);
when(connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(STATEMENT_ID)).thenReturn(null);
- assertThrows(FirebirdProtocolException.class, () -> new FirebirdBatchCreateCommandExecutor(packet, connectionSession).execute());
+ assertThrows(InvalidStatementHandleException.class, () -> new FirebirdBatchCreateCommandExecutor(packet, connectionSession).execute());
verify(packet, never()).getBatchBlr();
}
@@ -155,7 +176,18 @@ class FirebirdBatchCreateCommandExecutorTest {
when(packet.getStatementHandle()).thenReturn(STATEMENT_ID);
when(connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(STATEMENT_ID)).thenReturn(preparedStatement);
when(packet.getBatchBlr()).thenReturn(createEmptyBatchBlr());
- assertThrows(FirebirdProtocolException.class, () -> new FirebirdBatchCreateCommandExecutor(packet, connectionSession).execute());
+ assertThrows(BatchParametersRequiredException.class, () -> new FirebirdBatchCreateCommandExecutor(packet, connectionSession).execute());
+ }
+
+ @Test
+ void assertExecuteWhenMessageLengthMismatched() {
+ FirebirdBatchRegistry.getInstance().registerConnection(CONNECTION_ID);
+ when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
+ when(packet.getStatementHandle()).thenReturn(STATEMENT_ID);
+ when(connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(STATEMENT_ID)).thenReturn(preparedStatement);
+ when(packet.getBatchBlr()).thenReturn(createBatchBlr());
+ when(packet.getBatchMessageLength()).thenReturn(5L);
+ assertThrows(InvalidBatchMessageFormatException.class, () -> new FirebirdBatchCreateCommandExecutor(packet, connectionSession).execute());
}
@Test
@@ -189,6 +221,7 @@ class FirebirdBatchCreateCommandExecutorTest {
assertThat(actual.getVersion(), is(BATCH_VERSION_1));
assertThat(actual.getBufferSize(), is(DEFAULT_BUFFER_SIZE));
assertFalse(actual.isRecordCounts());
+ assertFalse(actual.isMultiError());
}
@Test
@@ -215,6 +248,18 @@ class FirebirdBatchCreateCommandExecutorTest {
assertTrue(actual.isRecordCounts());
}
+ @Test
+ void assertParseBatchParametersWithMultiError() {
+ FirebirdBatchCreateCommandExecutor.BatchParameters actual = FirebirdBatchCreateCommandExecutor.BatchParameters.parse(createBatchParametersBuffer(TAG_MULTIERROR, 1));
+ assertTrue(actual.isMultiError());
+ }
+
+ @Test
+ void assertParseBatchParametersWithDisabledMultiError() {
+ FirebirdBatchCreateCommandExecutor.BatchParameters actual = FirebirdBatchCreateCommandExecutor.BatchParameters.parse(createBatchParametersBuffer(TAG_MULTIERROR, 0));
+ assertFalse(actual.isMultiError());
+ }
+
@Test
void assertParseBatchParametersWithBlobPolicy() {
FirebirdProtocolException actual = assertThrows(FirebirdProtocolException.class,
@@ -224,7 +269,7 @@ class FirebirdBatchCreateCommandExecutorTest {
@Test
void assertParseBatchParametersWhenVersionInvalid() {
- assertThrows(FirebirdProtocolException.class, () -> FirebirdBatchCreateCommandExecutor.BatchParameters.parse(Unpooled.wrappedBuffer(new byte[]{2})));
+ assertThrows(InvalidBatchParameterVersionException.class, () -> FirebirdBatchCreateCommandExecutor.BatchParameters.parse(Unpooled.wrappedBuffer(new byte[]{2})));
}
@Test
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchExecuteCommandExecutorTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchExecuteCommandExecutorTest.java
index 637e43e3a7d..570b3ecd85c 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchExecuteCommandExecutorTest.java
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchExecuteCommandExecutorTest.java
@@ -23,11 +23,16 @@ import org.apache.shardingsphere.database.protocol.firebird.packet.command.query
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchCreateCommandPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchRegistry;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchStatement;
-import org.apache.shardingsphere.database.protocol.firebird.exception.FirebirdProtocolException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchHandleException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidTransactionHandleException;
import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdBatchCompletionStateResponse;
+import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdBatchCompletionStateResponse.DetailedError;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.FirebirdServerPreparedStatement;
+import org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction.FirebirdTransactionIdGenerator;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
@@ -41,6 +46,8 @@ import org.firebirdsql.gds.BlrConstants;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
@@ -80,10 +87,24 @@ class FirebirdBatchExecuteCommandExecutorTest {
@Mock
private FirebirdServerPreparedStatement preparedStatement;
+ private int transactionId;
+
+ @BeforeEach
+ void setUp() {
+ FirebirdTransactionIdGenerator.getInstance().registerConnection(CONNECTION_ID);
+ transactionId = FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID);
+ }
+
+ @AfterEach
+ void tearDown() {
+ FirebirdTransactionIdGenerator.getInstance().unregisterConnection(CONNECTION_ID);
+ }
+
@Test
void assertExecuteWithoutRecordCounts() throws ReflectiveOperationException, SQLException {
when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
when(packet.getStatementHandle()).thenReturn(STATEMENT_ID);
+ when(packet.getTransactionHandle()).thenReturn(transactionId);
when(batchStatement.getStatementHandle()).thenReturn(STATEMENT_ID);
when(batchStatement.getParameterValues()).thenReturn(Arrays.asList(Arrays.asList(1, "foo_1"), Arrays.asList(2, "foo_2")));
when(connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(STATEMENT_ID)).thenReturn(preparedStatement);
@@ -91,7 +112,7 @@ class FirebirdBatchExecuteCommandExecutorTest {
try (
MockedStatic<FirebirdBatchRegistry> mockedRegistry = mockStatic(FirebirdBatchRegistry.class);
MockedConstruction<FirebirdBatchedStatementsExecutor> ignored = mockConstruction(FirebirdBatchedStatementsExecutor.class,
- (mock, context) -> when(mock.executeBatch()).thenReturn(new int[]{5, 5}))) {
+ (mock, context) -> when(mock.executeBatch()).thenReturn(new FirebirdBatchCompletion(2, new int[]{5, 5})))) {
mockedRegistry.when(FirebirdBatchRegistry::getInstance).thenReturn(batchRegistry);
Collection<DatabasePacket> actual = new FirebirdBatchExecuteCommandExecutor(packet, connectionSession).execute();
assertThat(actual.size(), is(1));
@@ -109,6 +130,7 @@ class FirebirdBatchExecuteCommandExecutorTest {
void assertExecuteWithRecordCounts() throws ReflectiveOperationException, SQLException {
when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
when(packet.getStatementHandle()).thenReturn(STATEMENT_ID);
+ when(packet.getTransactionHandle()).thenReturn(transactionId);
when(batchStatement.getStatementHandle()).thenReturn(STATEMENT_ID);
when(batchStatement.isRecordCounts()).thenReturn(true);
when(batchStatement.getParameterValues()).thenReturn(Arrays.asList(Arrays.asList(1, "foo_1"), Arrays.asList(2, "foo_2")));
@@ -117,7 +139,7 @@ class FirebirdBatchExecuteCommandExecutorTest {
try (
MockedStatic<FirebirdBatchRegistry> mockedRegistry = mockStatic(FirebirdBatchRegistry.class);
MockedConstruction<FirebirdBatchedStatementsExecutor> ignored = mockConstruction(FirebirdBatchedStatementsExecutor.class,
- (mock, context) -> when(mock.executeBatch()).thenReturn(new int[]{5, 5}))) {
+ (mock, context) -> when(mock.executeBatch()).thenReturn(new FirebirdBatchCompletion(2, new int[]{5, 5})))) {
mockedRegistry.when(FirebirdBatchRegistry::getInstance).thenReturn(batchRegistry);
FirebirdBatchCompletionStateResponse actual = (FirebirdBatchCompletionStateResponse) new FirebirdBatchExecuteCommandExecutor(packet, connectionSession).execute().iterator().next();
assertThat(getRecordsCount(actual), is(2L));
@@ -130,6 +152,7 @@ class FirebirdBatchExecuteCommandExecutorTest {
void assertExecuteEmptyBatch() throws ReflectiveOperationException, SQLException {
when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
when(packet.getStatementHandle()).thenReturn(STATEMENT_ID);
+ when(packet.getTransactionHandle()).thenReturn(transactionId);
when(createPacket.getStatementHandle()).thenReturn(STATEMENT_ID);
when(createPacket.getBatchBlr()).thenReturn(createBatchBlr());
when(createPacket.getBatchMessageLength()).thenReturn(6L);
@@ -148,6 +171,61 @@ class FirebirdBatchExecuteCommandExecutorTest {
}
}
+ @Test
+ void assertExecuteWithFailedMessage() throws ReflectiveOperationException, SQLException {
+ when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
+ when(packet.getStatementHandle()).thenReturn(STATEMENT_ID);
+ when(packet.getTransactionHandle()).thenReturn(transactionId);
+ when(batchStatement.getStatementHandle()).thenReturn(STATEMENT_ID);
+ when(batchStatement.isRecordCounts()).thenReturn(true);
+ when(batchStatement.getParameterValues()).thenReturn(Arrays.asList(Arrays.asList(1, "foo_1"), Arrays.asList(2, "foo_2"), Arrays.asList(3, "foo_3")));
+ when(connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(STATEMENT_ID)).thenReturn(preparedStatement);
+ when(batchRegistry.getBatchStatement(CONNECTION_ID, STATEMENT_ID)).thenReturn(batchStatement);
+ SQLException failureCause = new SQLException("violation of PRIMARY or UNIQUE KEY constraint", "23000", 335544665);
+ FirebirdBatchCompletion completion = new FirebirdBatchCompletion(
+ 2, new int[]{1, FirebirdBatchCompletion.EXECUTE_FAILED}, Collections.singletonList(new FirebirdBatchCompletion.Failure(1, failureCause)));
+ try (
+ MockedStatic<FirebirdBatchRegistry> mockedRegistry = mockStatic(FirebirdBatchRegistry.class);
+ MockedConstruction<FirebirdBatchedStatementsExecutor> ignored = mockConstruction(FirebirdBatchedStatementsExecutor.class,
+ (mock, context) -> when(mock.executeBatch()).thenReturn(completion))) {
+ mockedRegistry.when(FirebirdBatchRegistry::getInstance).thenReturn(batchRegistry);
+ FirebirdBatchCompletionStateResponse actual = (FirebirdBatchCompletionStateResponse) new FirebirdBatchExecuteCommandExecutor(packet, connectionSession).execute().iterator().next();
+ assertThat(getRecordsCount(actual), is(2L));
+ assertArrayEquals(new int[]{1, FirebirdBatchCompletion.EXECUTE_FAILED}, getUpdateCounts(actual));
+ List<DetailedError> detailedErrors = getDetailedErrors(actual);
+ assertThat(detailedErrors.size(), is(1));
+ assertThat(detailedErrors.get(0).getElement(), is(1));
+ verify(batchStatement).reset();
+ }
+ }
+
+ @Test
+ void assertExecuteWithFailedMessageWithoutRecordCounts() throws ReflectiveOperationException, SQLException {
+ when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
+ when(packet.getStatementHandle()).thenReturn(STATEMENT_ID);
+ when(packet.getTransactionHandle()).thenReturn(transactionId);
+ when(batchStatement.getStatementHandle()).thenReturn(STATEMENT_ID);
+ when(batchStatement.getParameterValues()).thenReturn(Arrays.asList(Arrays.asList(1, "foo_1"), Arrays.asList(2, "foo_2")));
+ when(connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(STATEMENT_ID)).thenReturn(preparedStatement);
+ when(batchRegistry.getBatchStatement(CONNECTION_ID, STATEMENT_ID)).thenReturn(batchStatement);
+ SQLException failureCause = new SQLException("violation", "23000", 335544665);
+ FirebirdBatchCompletion completion = new FirebirdBatchCompletion(
+ 1, new int[]{FirebirdBatchCompletion.EXECUTE_FAILED}, Collections.singletonList(new FirebirdBatchCompletion.Failure(0, failureCause)));
+ try (
+ MockedStatic<FirebirdBatchRegistry> mockedRegistry = mockStatic(FirebirdBatchRegistry.class);
+ MockedConstruction<FirebirdBatchedStatementsExecutor> ignored = mockConstruction(FirebirdBatchedStatementsExecutor.class,
+ (mock, context) -> when(mock.executeBatch()).thenReturn(completion))) {
+ mockedRegistry.when(FirebirdBatchRegistry::getInstance).thenReturn(batchRegistry);
+ FirebirdBatchCompletionStateResponse actual = (FirebirdBatchCompletionStateResponse) new FirebirdBatchExecuteCommandExecutor(packet, connectionSession).execute().iterator().next();
+ assertThat(getRecordsCount(actual), is(1L));
+ assertArrayEquals(new int[0], getUpdateCounts(actual));
+ List<DetailedError> detailedErrors = getDetailedErrors(actual);
+ assertThat(detailedErrors.size(), is(1));
+ assertThat(detailedErrors.get(0).getElement(), is(0));
+ verify(batchStatement).reset();
+ }
+ }
+
@Test
void assertExecuteWithNoBatchStatement() throws SQLException {
when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
@@ -156,12 +234,28 @@ class FirebirdBatchExecuteCommandExecutorTest {
try (MockedStatic<FirebirdBatchRegistry> mockedRegistry = mockStatic(FirebirdBatchRegistry.class)) {
mockedRegistry.when(FirebirdBatchRegistry::getInstance).thenReturn(batchRegistry);
FirebirdBatchExecuteCommandExecutor executor = new FirebirdBatchExecuteCommandExecutor(packet, connectionSession);
- assertThrows(FirebirdProtocolException.class, executor::execute);
+ assertThrows(InvalidBatchHandleException.class, executor::execute);
verify(batchRegistry).getBatchStatement(CONNECTION_ID, STATEMENT_ID);
verify(connectionSession.getServerPreparedStatementRegistry(), never()).getPreparedStatement(anyInt());
}
}
+ @Test
+ void assertExecuteWithClosedTransactionHandle() {
+ when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
+ when(packet.getStatementHandle()).thenReturn(STATEMENT_ID);
+ when(packet.getTransactionHandle()).thenReturn(transactionId);
+ when(batchRegistry.getBatchStatement(CONNECTION_ID, STATEMENT_ID)).thenReturn(batchStatement);
+ FirebirdTransactionIdGenerator.getInstance().closeTransaction(CONNECTION_ID, transactionId);
+ FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID);
+ try (MockedStatic<FirebirdBatchRegistry> mockedRegistry = mockStatic(FirebirdBatchRegistry.class)) {
+ mockedRegistry.when(FirebirdBatchRegistry::getInstance).thenReturn(batchRegistry);
+ FirebirdBatchExecuteCommandExecutor executor = new FirebirdBatchExecuteCommandExecutor(packet, connectionSession);
+ assertThrows(InvalidTransactionHandleException.class, executor::execute);
+ verify(connectionSession.getServerPreparedStatementRegistry(), never()).getPreparedStatement(anyInt());
+ }
+ }
+
private long getRecordsCount(final FirebirdBatchCompletionStateResponse response) throws ReflectiveOperationException {
return (Long) Plugins.getMemberAccessor().get(FirebirdBatchCompletionStateResponse.class.getDeclaredField("recordsCount"), response);
}
@@ -175,4 +269,9 @@ class FirebirdBatchExecuteCommandExecutorTest {
(byte) BlrConstants.blr_version5, (byte) BlrConstants.blr_begin, (byte) BlrConstants.blr_message, 0,
2, 0, (byte) BlrConstants.blr_long, 0, (byte) BlrConstants.blr_short, 0, (byte) BlrConstants.blr_end, (byte) BlrConstants.blr_eoc});
}
+
+ @SuppressWarnings("unchecked")
+ private List<DetailedError> getDetailedErrors(final FirebirdBatchCompletionStateResponse response) throws ReflectiveOperationException {
+ return (List<DetailedError>) Plugins.getMemberAccessor().get(FirebirdBatchCompletionStateResponse.class.getDeclaredField("detailedErrors"), response);
+ }
}
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchMessageCommandExecutorTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchMessageCommandExecutorTest.java
index ff8bedddf59..cd439bd864f 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchMessageCommandExecutorTest.java
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchMessageCommandExecutorTest.java
@@ -20,25 +20,30 @@ package org.apache.shardingsphere.proxy.frontend.firebird.command.query.batch;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.BatchTooBigException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidBatchHandleException;
import org.apache.shardingsphere.database.protocol.constant.CommonConstants;
import org.apache.shardingsphere.database.protocol.firebird.codec.FirebirdPacketCodecEngine;
import org.apache.shardingsphere.database.protocol.firebird.constant.FirebirdConstant;
import org.apache.shardingsphere.database.protocol.firebird.constant.protocol.FirebirdProtocolVersion;
-import org.apache.shardingsphere.database.protocol.firebird.exception.FirebirdProtocolException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.FirebirdCommandPacketType;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.FirebirdBinaryColumnType;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchColumnDescriptor;
-import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchRegistry;
+import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchExecuteCommandPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchMessageCommandPacket;
+import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchRegistry;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.batch.FirebirdBatchStatement;
import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket;
import org.apache.shardingsphere.database.protocol.firebird.payload.FirebirdPacketPayload;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
+import org.apache.shardingsphere.proxy.frontend.firebird.command.query.FirebirdServerPreparedStatement;
+import org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction.FirebirdTransactionIdGenerator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.mockito.Mock;
+import org.mockito.MockedConstruction;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
@@ -53,7 +58,9 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isA;
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.mockConstruction;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -70,6 +77,12 @@ class FirebirdBatchMessageCommandExecutorTest {
private FirebirdBatchMessageCommandPacket packet;
@Mock
+ private FirebirdBatchExecuteCommandPacket executePacket;
+
+ @Mock
+ private FirebirdServerPreparedStatement preparedStatement;
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
private ConnectionSession connectionSession;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -106,7 +119,7 @@ class FirebirdBatchMessageCommandExecutorTest {
when(batchRegistry.getBatchStatement(CONNECTION_ID, STATEMENT_ID)).thenReturn(null);
try (MockedStatic<FirebirdBatchRegistry> mockedRegistry = mockStatic(FirebirdBatchRegistry.class)) {
mockedRegistry.when(FirebirdBatchRegistry::getInstance).thenReturn(batchRegistry);
- assertThrows(FirebirdProtocolException.class, () -> new FirebirdBatchMessageCommandExecutor(packet, connectionSession).execute());
+ assertThrows(InvalidBatchHandleException.class, () -> new FirebirdBatchMessageCommandExecutor(packet, connectionSession).execute());
}
}
@@ -120,13 +133,14 @@ class FirebirdBatchMessageCommandExecutorTest {
when(batchRegistry.getBatchStatement(CONNECTION_ID, STATEMENT_ID)).thenReturn(batchStatement);
try (MockedStatic<FirebirdBatchRegistry> mockedRegistry = mockStatic(FirebirdBatchRegistry.class)) {
mockedRegistry.when(FirebirdBatchRegistry::getInstance).thenReturn(batchRegistry);
- assertThrows(FirebirdProtocolException.class, () -> new FirebirdBatchMessageCommandExecutor(packet, connectionSession).execute());
+ assertThrows(BatchTooBigException.class, () -> new FirebirdBatchMessageCommandExecutor(packet, connectionSession).execute());
verify(batchStatement, never()).addSize(9);
+ verify(batchStatement, never()).reset();
}
}
@Test
- void assertExecuteDoesNotStoreRejectedCoalescedBatchMessage() throws SQLException {
+ void assertExecuteAfterRejectedOversizedSendKeepsAcceptedMessages() throws SQLException {
when(context.channel().attr(CommonConstants.CHARSET_ATTRIBUTE_KEY).get()).thenReturn(StandardCharsets.UTF_8);
when(context.channel().attr(FirebirdConstant.CONNECTION_PROTOCOL_VERSION).get()).thenReturn(FirebirdProtocolVersion.PROTOCOL_VERSION10);
when(context.channel().attr(FirebirdConstant.CURRENT_CONNECTION).get()).thenReturn(CONNECTION_ID);
@@ -140,13 +154,37 @@ class FirebirdBatchMessageCommandExecutorTest {
new FirebirdPacketCodecEngine().decode(context, buildBatchMessages(), out);
assertThat(out.size(), is(2));
new FirebirdBatchMessageCommandExecutor(createBatchSendMessagePacket((ByteBuf) out.get(0)), connectionSession).execute();
- assertThrows(FirebirdProtocolException.class, () -> new FirebirdBatchMessageCommandExecutor(createBatchSendMessagePacket((ByteBuf) out.get(1)), connectionSession).execute());
assertThat(batchStatement.getParameterValues().size(), is(1));
assertThat(batchStatement.getParameterValues().get(0), is(Collections.singletonList(100)));
assertThat(batchStatement.getAccumulatedSize(), is(8L));
+ assertThrows(BatchTooBigException.class, () -> new FirebirdBatchMessageCommandExecutor(createBatchSendMessagePacket((ByteBuf) out.get(1)), connectionSession).execute());
+ assertThat(batchStatement.getParameterValues(), is(Collections.singletonList(Collections.singletonList(100))));
+ assertThat(batchStatement.getAccumulatedSize(), is(8L));
+ assertExecuteExecutesAcceptedMessages(batchStatement);
} finally {
FirebirdBatchRegistry.getInstance().unregisterConnection(CONNECTION_ID);
+ FirebirdTransactionIdGenerator.getInstance().unregisterConnection(CONNECTION_ID);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void assertExecuteExecutesAcceptedMessages(final FirebirdBatchStatement batchStatement) throws SQLException {
+ FirebirdTransactionIdGenerator.getInstance().registerConnection(CONNECTION_ID);
+ when(executePacket.getStatementHandle()).thenReturn(STATEMENT_ID);
+ when(executePacket.getTransactionHandle()).thenReturn(FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID));
+ when(connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(STATEMENT_ID)).thenReturn(preparedStatement);
+ List<List<List<Object>>> executedParameterValues = new LinkedList<>();
+ try (
+ MockedConstruction<FirebirdBatchedStatementsExecutor> ignored = mockConstruction(FirebirdBatchedStatementsExecutor.class,
+ (mock, mockContext) -> {
+ executedParameterValues.add(new LinkedList<>((List<List<Object>>) mockContext.arguments().get(2)));
+ when(mock.executeBatch()).thenReturn(new FirebirdBatchCompletion(1, new int[]{1}));
+ })) {
+ new FirebirdBatchExecuteCommandExecutor(executePacket, connectionSession).execute();
}
+ assertThat(executedParameterValues, is(Collections.singletonList(Collections.singletonList(Collections.singletonList(100)))));
+ assertTrue(batchStatement.getParameterValues().isEmpty());
+ assertThat(batchStatement.getAccumulatedSize(), is(0L));
}
private FirebirdBatchMessageCommandPacket createBatchSendMessagePacket(final ByteBuf byteBuf) {
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchedStatementsExecutorTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchedStatementsExecutorTest.java
index 08aae4e7a03..bf9d5af7dca 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchedStatementsExecutorTest.java
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchedStatementsExecutorTest.java
@@ -62,18 +62,23 @@ import org.mockito.MockedConstruction;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
+import java.sql.BatchUpdateException;
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.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -82,6 +87,9 @@ import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(AutoMockExtension.class)
@@ -104,7 +112,7 @@ class FirebirdBatchedStatementsExecutorTest {
when(databaseConnectionManager.getConnections(any(), nullable(String.class), anyInt(), anyInt(), any(ConnectionMode.class))).thenReturn(Collections.singletonList(connection));
PreparedStatement preparedStatement = mock(PreparedStatement.class);
when(preparedStatement.getConnection()).thenReturn(connection);
- when(preparedStatement.executeBatch()).thenReturn(new int[]{1, 2});
+ when(preparedStatement.executeBatch()).thenReturn(new int[]{1}, new int[]{2});
when(backendStatement.createStorageResource(any(ExecutionUnit.class), eq(connection), anyInt(), any(ConnectionMode.class), any(StatementOption.class), nullable(DatabaseType.class)))
.thenReturn(preparedStatement);
ContextManager contextManager = mockContextManager();
@@ -112,8 +120,10 @@ class FirebirdBatchedStatementsExecutorTest {
FirebirdServerPreparedStatement firebirdPreparedStatement = new FirebirdServerPreparedStatement("INSERT INTO t (id, col) VALUES (?, ?)",
mockInsertStatementContext(), new HintValueContext());
List<List<Object>> parameterSets = Arrays.asList(Arrays.asList(1, "foo_1"), Arrays.asList(2, "foo_2"));
- int[] actual = new FirebirdBatchedStatementsExecutor(mockConnectionSession(), firebirdPreparedStatement, parameterSets).executeBatch();
- assertArrayEquals(new int[]{1, 2}, actual);
+ FirebirdBatchCompletion actual = new FirebirdBatchedStatementsExecutor(mockConnectionSession(), firebirdPreparedStatement, parameterSets, false).executeBatch();
+ assertThat(actual.getRecordsCount(), is(2));
+ assertArrayEquals(new int[]{1, 2}, actual.getUpdateCounts());
+ assertTrue(actual.getFailures().isEmpty());
InOrder inOrder = inOrder(preparedStatement);
for (List<Object> each : parameterSets) {
inOrder.verify(preparedStatement).setObject(1, each.get(0));
@@ -148,10 +158,320 @@ class FirebirdBatchedStatementsExecutorTest {
MockedConstruction<KernelProcessor> ignored = mockConstruction(KernelProcessor.class,
(mock, context) -> when(mock.generateExecutionContext(any(), any(), any()))
.thenReturn(new ExecutionContext(null, Arrays.asList(firstExecutionUnit, secondExecutionUnit), mock(RouteContext.class))))) {
- assertArrayEquals(new int[]{2}, new FirebirdBatchedStatementsExecutor(mockConnectionSession(), firebirdPreparedStatement, Collections.singletonList(params)).executeBatch());
+ FirebirdBatchCompletion actual = new FirebirdBatchedStatementsExecutor(mockConnectionSession(), firebirdPreparedStatement, Collections.singletonList(params), false).executeBatch();
+ assertThat(actual.getRecordsCount(), is(1));
+ assertArrayEquals(new int[]{2}, actual.getUpdateCounts());
+ assertTrue(actual.getFailures().isEmpty());
}
}
+ @Test
+ void assertExecuteBatchWithMultiRouteFailureTranslatesToClientIndexes() throws SQLException {
+ Connection connection = mock(Connection.class, RETURNS_DEEP_STUBS);
+ when(connection.getMetaData().getURL()).thenReturn("jdbc:firebirdsql://127.0.0.1/db");
+ when(databaseConnectionManager.getConnections(any(), nullable(String.class), anyInt(), anyInt(), any(ConnectionMode.class))).thenReturn(Collections.singletonList(connection));
+ PreparedStatement firstPreparedStatement = mock(PreparedStatement.class);
+ PreparedStatement secondPreparedStatement = mock(PreparedStatement.class);
+ when(firstPreparedStatement.getConnection()).thenReturn(connection);
+ when(secondPreparedStatement.getConnection()).thenReturn(connection);
+ when(firstPreparedStatement.executeBatch()).thenReturn(new int[]{1});
+ BatchUpdateException failure = new BatchUpdateException("violation of PRIMARY or UNIQUE KEY constraint", "23000", 335544665, new int[0]);
+ when(secondPreparedStatement.executeBatch()).thenThrow(failure);
+ when(backendStatement.createStorageResource(any(ExecutionUnit.class), eq(connection), anyInt(), any(ConnectionMode.class), any(StatementOption.class), nullable(DatabaseType.class)))
+ .thenReturn(firstPreparedStatement, secondPreparedStatement);
+ ContextManager contextManager = mockContextManager("ds_0", "ds_1");
+ when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+ FirebirdServerPreparedStatement firebirdPreparedStatement = new FirebirdServerPreparedStatement("INSERT INTO t (id, col) VALUES (?, ?)",
+ mockInsertStatementContext(), new HintValueContext());
+ List<Object> firstParams = Arrays.asList(1, "foo_1");
+ List<Object> secondParams = Arrays.asList(2, "foo_2");
+ ExecutionUnit firstExecutionUnit = new ExecutionUnit("ds_0", new SQLUnit(firebirdPreparedStatement.getSql(), firstParams));
+ ExecutionUnit secondExecutionUnit = new ExecutionUnit("ds_1", new SQLUnit(firebirdPreparedStatement.getSql(), secondParams));
+ try (
+ MockedConstruction<KernelProcessor> ignored = mockConstruction(KernelProcessor.class,
+ (mock, context) -> when(mock.generateExecutionContext(any(), any(), any()))
+ .thenReturn(new ExecutionContext(null, Collections.singletonList(firstExecutionUnit), mock(RouteContext.class)),
+ new ExecutionContext(null, Collections.singletonList(secondExecutionUnit), mock(RouteContext.class))))) {
+ FirebirdBatchCompletion actual = new FirebirdBatchedStatementsExecutor(mockConnectionSession(), firebirdPreparedStatement,
+ Arrays.asList(firstParams, secondParams), false).executeBatch();
+ assertThat(actual.getRecordsCount(), is(2));
+ assertArrayEquals(new int[]{1, FirebirdBatchCompletion.EXECUTE_FAILED}, actual.getUpdateCounts());
+ assertThat(actual.getFailures().size(), is(1));
+ FirebirdBatchCompletion.Failure actualFailure = actual.getFailures().iterator().next();
+ assertThat(actualFailure.getMessageIndex(), is(1));
+ assertThat(actualFailure.getCause(), is(failure));
+ }
+ }
+
+ @Test
+ void assertExecuteBatchWithoutMultiErrorHaltsAtFirstFailedMessage() throws SQLException {
+ Connection connection = mock(Connection.class, RETURNS_DEEP_STUBS);
+ when(connection.getMetaData().getURL()).thenReturn("jdbc:firebirdsql://127.0.0.1/db");
+ when(databaseConnectionManager.getConnections(any(), nullable(String.class), anyInt(), anyInt(), any(ConnectionMode.class))).thenReturn(Collections.singletonList(connection));
+ PreparedStatement firstPreparedStatement = mock(PreparedStatement.class);
+ PreparedStatement secondPreparedStatement = mock(PreparedStatement.class);
+ when(firstPreparedStatement.getConnection()).thenReturn(connection);
+ when(secondPreparedStatement.getConnection()).thenReturn(connection);
+ BatchUpdateException failure = new BatchUpdateException("violation of PRIMARY or UNIQUE KEY constraint", "23000", 335544665, new int[0]);
+ when(firstPreparedStatement.executeBatch()).thenThrow(failure);
+ when(secondPreparedStatement.executeBatch()).thenReturn(new int[]{1});
+ when(backendStatement.createStorageResource(any(ExecutionUnit.class), eq(connection), anyInt(), any(ConnectionMode.class), any(StatementOption.class), nullable(DatabaseType.class)))
+ .thenReturn(firstPreparedStatement, secondPreparedStatement);
+ ContextManager contextManager = mockContextManager("ds_0", "ds_1");
+ when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+ FirebirdServerPreparedStatement firebirdPreparedStatement = new FirebirdServerPreparedStatement("INSERT INTO t (id, col) VALUES (?, ?)",
+ mockInsertStatementContext(), new HintValueContext());
+ List<Object> firstParams = Arrays.asList(1, "foo_1");
+ List<Object> secondParams = Arrays.asList(2, "foo_2");
+ ExecutionUnit firstExecutionUnit = new ExecutionUnit("ds_0", new SQLUnit(firebirdPreparedStatement.getSql(), firstParams));
+ ExecutionUnit secondExecutionUnit = new ExecutionUnit("ds_1", new SQLUnit(firebirdPreparedStatement.getSql(), secondParams));
+ try (
+ MockedConstruction<KernelProcessor> ignored = mockConstruction(KernelProcessor.class,
+ (mock, context) -> when(mock.generateExecutionContext(any(), any(), any()))
+ .thenReturn(new ExecutionContext(null, Collections.singletonList(firstExecutionUnit), mock(RouteContext.class)),
+ new ExecutionContext(null, Collections.singletonList(secondExecutionUnit), mock(RouteContext.class))))) {
+ FirebirdBatchCompletion actual = new FirebirdBatchedStatementsExecutor(mockConnectionSession(), firebirdPreparedStatement,
+ Arrays.asList(firstParams, secondParams), false).executeBatch();
+ verify(secondPreparedStatement, never()).executeBatch();
+ verify(secondPreparedStatement).close();
+ assertThat(actual.getRecordsCount(), is(1));
+ assertArrayEquals(new int[]{FirebirdBatchCompletion.EXECUTE_FAILED}, actual.getUpdateCounts());
+ assertThat(actual.getFailures().size(), is(1));
+ FirebirdBatchCompletion.Failure actualFailure = actual.getFailures().iterator().next();
+ assertThat(actualFailure.getMessageIndex(), is(0));
+ assertThat(actualFailure.getCause(), is(failure));
+ }
+ }
+
+ @Test
+ void assertExecuteBatchWithMultiErrorReportsMessagesAfterFailedRoute() throws SQLException {
+ Connection connection = mock(Connection.class, RETURNS_DEEP_STUBS);
+ when(connection.getMetaData().getURL()).thenReturn("jdbc:firebirdsql://127.0.0.1/db");
+ when(databaseConnectionManager.getConnections(any(), nullable(String.class), anyInt(), anyInt(), any(ConnectionMode.class))).thenReturn(Collections.singletonList(connection));
+ PreparedStatement firstPreparedStatement = mock(PreparedStatement.class);
+ PreparedStatement secondPreparedStatement = mock(PreparedStatement.class);
+ when(firstPreparedStatement.getConnection()).thenReturn(connection);
+ when(secondPreparedStatement.getConnection()).thenReturn(connection);
+ BatchUpdateException failure = new BatchUpdateException("violation of PRIMARY or UNIQUE KEY constraint", "23000", 335544665, new int[0]);
+ when(firstPreparedStatement.executeBatch()).thenThrow(failure);
+ when(secondPreparedStatement.executeBatch()).thenReturn(new int[]{1});
+ when(backendStatement.createStorageResource(any(ExecutionUnit.class), eq(connection), anyInt(), any(ConnectionMode.class), any(StatementOption.class), nullable(DatabaseType.class)))
+ .thenReturn(firstPreparedStatement, secondPreparedStatement);
+ ContextManager contextManager = mockContextManager("ds_0", "ds_1");
+ when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+ FirebirdServerPreparedStatement firebirdPreparedStatement = new FirebirdServerPreparedStatement("INSERT INTO t (id, col) VALUES (?, ?)",
+ mockInsertStatementContext(), new HintValueContext());
+ List<Object> firstParams = Arrays.asList(1, "foo_1");
+ List<Object> secondParams = Arrays.asList(2, "foo_2");
+ ExecutionUnit firstExecutionUnit = new ExecutionUnit("ds_0", new SQLUnit(firebirdPreparedStatement.getSql(), firstParams));
+ ExecutionUnit secondExecutionUnit = new ExecutionUnit("ds_1", new SQLUnit(firebirdPreparedStatement.getSql(), secondParams));
+ try (
+ MockedConstruction<KernelProcessor> ignored = mockConstruction(KernelProcessor.class,
+ (mock, context) -> when(mock.generateExecutionContext(any(), any(), any()))
+ .thenReturn(new ExecutionContext(null, Collections.singletonList(firstExecutionUnit), mock(RouteContext.class)),
+ new ExecutionContext(null, Collections.singletonList(secondExecutionUnit), mock(RouteContext.class))))) {
+ FirebirdBatchCompletion actual = new FirebirdBatchedStatementsExecutor(mockConnectionSession(), firebirdPreparedStatement,
+ Arrays.asList(firstParams, secondParams), true).executeBatch();
+ assertThat(actual.getRecordsCount(), is(2));
+ assertArrayEquals(new int[]{FirebirdBatchCompletion.EXECUTE_FAILED, 1}, actual.getUpdateCounts());
+ assertThat(actual.getFailures().size(), is(1));
+ FirebirdBatchCompletion.Failure actualFailure = actual.getFailures().iterator().next();
+ assertThat(actualFailure.getMessageIndex(), is(0));
+ assertThat(actualFailure.getCause(), is(failure));
+ }
+ }
+
+ @Test
+ void assertExecuteBatchWithoutMultiErrorStopsSameRouteAtFailedMessage() throws SQLException {
+ PreparedStatement preparedStatement = mockSameRoutePreparedStatement();
+ when(preparedStatement.executeBatch()).thenReturn(new int[]{1}).thenThrow(createFailure(new int[0]));
+ FirebirdBatchCompletion actual = executeSameRouteBatch(preparedStatement, false);
+ verify(preparedStatement, times(2)).executeBatch();
+ assertThat(actual.getRecordsCount(), is(2));
+ assertArrayEquals(new int[]{1, FirebirdBatchCompletion.EXECUTE_FAILED}, actual.getUpdateCounts());
+ assertThat(actual.getFailures().size(), is(1));
+ assertThat(actual.getFailures().iterator().next().getMessageIndex(), is(1));
+ }
+
+ @Test
+ void assertExecuteBatchWithoutMultiErrorNeverSubmitsMessageAfterFailedMessageOnSameRoute() throws SQLException {
+ PreparedStatement preparedStatement = mockSameRoutePreparedStatement();
+ BatchUpdateException failure = createFailure(new int[0]);
+ when(preparedStatement.executeBatch()).thenReturn(new int[]{1}).thenThrow(failure);
+ FirebirdBatchCompletion actual = executeSameRouteBatch(preparedStatement, false);
+
+ assertThat(actual.getRecordsCount(), is(2));
+ assertArrayEquals(new int[]{1, FirebirdBatchCompletion.EXECUTE_FAILED}, actual.getUpdateCounts());
+ assertThat(actual.getFailures().size(), is(1));
+ FirebirdBatchCompletion.Failure actualFailure = actual.getFailures().iterator().next();
+ assertThat(actualFailure.getMessageIndex(), is(1));
+ assertThat(actualFailure.getCause(), is(failure));
+ verify(preparedStatement, times(2)).addBatch();
+ verify(preparedStatement, times(2)).executeBatch();
+ verify(preparedStatement, never()).setObject(1, 3);
+ verify(preparedStatement, never()).setObject(2, "foo_3");
+ }
+
+ @Test
+ void assertExecuteBatchWithMultiErrorContinuesSameRouteAfterFailedMessage() throws SQLException {
+ PreparedStatement preparedStatement = mockSameRoutePreparedStatement();
+ when(preparedStatement.executeBatch()).thenThrow(createFailure(new int[]{1})).thenReturn(new int[]{1});
+ FirebirdBatchCompletion actual = executeSameRouteBatch(preparedStatement, true);
+ assertThat(actual.getRecordsCount(), is(3));
+ assertArrayEquals(new int[]{1, FirebirdBatchCompletion.EXECUTE_FAILED, 1}, actual.getUpdateCounts());
+ assertThat(actual.getFailures().size(), is(1));
+ assertThat(actual.getFailures().iterator().next().getMessageIndex(), is(1));
+ verify(preparedStatement, times(2)).executeBatch();
+ verify(preparedStatement).clearBatch();
+ verify(preparedStatement, times(4)).addBatch();
+ verify(preparedStatement, times(2)).setObject(1, 3);
+ }
+
+ @Test
+ void assertExecuteBatchWithNonBatchFailure() throws SQLException {
+ PreparedStatement preparedStatement = mockSameRoutePreparedStatement();
+ SQLException failure = new SQLException("connection lost", "08006", 335544721);
+ when(preparedStatement.executeBatch()).thenThrow(failure);
+ FirebirdBatchCompletion actual = executeSameRouteBatch(preparedStatement, false);
+ assertThat(actual.getRecordsCount(), is(1));
+ assertArrayEquals(new int[]{FirebirdBatchCompletion.EXECUTE_FAILED}, actual.getUpdateCounts());
+ assertThat(actual.getFailures().size(), is(1));
+ FirebirdBatchCompletion.Failure actualFailure = actual.getFailures().iterator().next();
+ assertThat(actualFailure.getMessageIndex(), is(0));
+ assertThat(actualFailure.getCause(), is(failure));
+ }
+
+ @Test
+ void assertExecuteBatchWithMultiErrorReportsChainedCausePerMessage() throws SQLException {
+ PreparedStatement preparedStatement = mockSameRoutePreparedStatement();
+ BatchUpdateException firstFailure = createFailure(new int[]{1});
+ SQLException firstCause = new SQLException("first violation", "23000", 335544665);
+ firstFailure.setNextException(firstCause);
+ BatchUpdateException secondFailure = createFailure(new int[0]);
+ SQLException secondCause = new SQLException("second violation", "23000", 335544349);
+ secondFailure.setNextException(secondCause);
+ when(preparedStatement.executeBatch()).thenThrow(firstFailure).thenThrow(secondFailure);
+ FirebirdBatchCompletion actual = executeSameRouteBatch(preparedStatement, true);
+ assertThat(actual.getRecordsCount(), is(3));
+ assertArrayEquals(new int[]{1, FirebirdBatchCompletion.EXECUTE_FAILED, FirebirdBatchCompletion.EXECUTE_FAILED}, actual.getUpdateCounts());
+ Iterator<FirebirdBatchCompletion.Failure> actualFailures = actual.getFailures().iterator();
+ FirebirdBatchCompletion.Failure actualFirstFailure = actualFailures.next();
+ assertThat(actualFirstFailure.getMessageIndex(), is(1));
+ assertThat(actualFirstFailure.getCause(), is(firstCause));
+ FirebirdBatchCompletion.Failure actualSecondFailure = actualFailures.next();
+ assertThat(actualSecondFailure.getMessageIndex(), is(2));
+ assertThat(actualSecondFailure.getCause(), is(secondCause));
+ }
+
+ @Test
+ void assertExecuteBatchWithInterleavedRoutesStopsBeforeLaterMessageOfEarlierRoute() throws SQLException {
+ PreparedStatement firstPreparedStatement = mock(PreparedStatement.class);
+ PreparedStatement secondPreparedStatement = mock(PreparedStatement.class);
+ BatchUpdateException failure = createFailure(new int[0]);
+ when(firstPreparedStatement.executeBatch()).thenReturn(new int[]{1});
+ when(secondPreparedStatement.executeBatch()).thenThrow(failure);
+ FirebirdBatchCompletion actual = executeInterleavedRoutesBatch(firstPreparedStatement, secondPreparedStatement);
+ assertThat(actual.getRecordsCount(), is(2));
+ assertArrayEquals(new int[]{1, FirebirdBatchCompletion.EXECUTE_FAILED}, actual.getUpdateCounts());
+ assertThat(actual.getFailures().size(), is(1));
+ FirebirdBatchCompletion.Failure actualFailure = actual.getFailures().iterator().next();
+ assertThat(actualFailure.getMessageIndex(), is(1));
+ assertThat(actualFailure.getCause(), is(failure));
+ InOrder inOrder = inOrder(firstPreparedStatement, secondPreparedStatement);
+ inOrder.verify(firstPreparedStatement).setObject(1, 1);
+ inOrder.verify(firstPreparedStatement).addBatch();
+ inOrder.verify(firstPreparedStatement).executeBatch();
+ inOrder.verify(secondPreparedStatement).setObject(1, 2);
+ inOrder.verify(secondPreparedStatement).addBatch();
+ inOrder.verify(secondPreparedStatement).executeBatch();
+ verify(firstPreparedStatement).addBatch();
+ verify(secondPreparedStatement).addBatch();
+ verify(firstPreparedStatement, never()).setObject(1, 3);
+ verify(firstPreparedStatement).executeBatch();
+ verify(secondPreparedStatement).executeBatch();
+ }
+
+ @Test
+ void assertExecuteBatchWithInterleavedRoutesReportsFailureOfLastMessage() throws SQLException {
+ PreparedStatement firstPreparedStatement = mock(PreparedStatement.class);
+ PreparedStatement secondPreparedStatement = mock(PreparedStatement.class);
+ BatchUpdateException failure = createFailure(new int[0]);
+ when(firstPreparedStatement.executeBatch()).thenReturn(new int[]{1}).thenThrow(failure);
+ when(secondPreparedStatement.executeBatch()).thenReturn(new int[]{1});
+ FirebirdBatchCompletion actual = executeInterleavedRoutesBatch(firstPreparedStatement, secondPreparedStatement);
+ assertThat(actual.getRecordsCount(), is(3));
+ assertArrayEquals(new int[]{1, 1, FirebirdBatchCompletion.EXECUTE_FAILED}, actual.getUpdateCounts());
+ assertThat(actual.getFailures().size(), is(1));
+ FirebirdBatchCompletion.Failure actualFailure = actual.getFailures().iterator().next();
+ assertThat(actualFailure.getMessageIndex(), is(2));
+ assertThat(actualFailure.getCause(), is(failure));
+ InOrder inOrder = inOrder(firstPreparedStatement, secondPreparedStatement);
+ inOrder.verify(firstPreparedStatement).setObject(1, 1);
+ inOrder.verify(firstPreparedStatement).addBatch();
+ inOrder.verify(firstPreparedStatement).executeBatch();
+ inOrder.verify(secondPreparedStatement).setObject(1, 2);
+ inOrder.verify(secondPreparedStatement).addBatch();
+ inOrder.verify(secondPreparedStatement).executeBatch();
+ inOrder.verify(firstPreparedStatement).setObject(1, 3);
+ inOrder.verify(firstPreparedStatement).addBatch();
+ inOrder.verify(firstPreparedStatement).executeBatch();
+ verify(firstPreparedStatement).setObject(1, 3);
+ verify(firstPreparedStatement, times(2)).addBatch();
+ verify(secondPreparedStatement).addBatch();
+ verify(firstPreparedStatement, times(2)).executeBatch();
+ verify(secondPreparedStatement).executeBatch();
+ }
+
+ private FirebirdBatchCompletion executeInterleavedRoutesBatch(final PreparedStatement firstPreparedStatement, final PreparedStatement secondPreparedStatement) throws SQLException {
+ Connection connection = mock(Connection.class, RETURNS_DEEP_STUBS);
+ when(connection.getMetaData().getURL()).thenReturn("jdbc:firebirdsql://127.0.0.1/db");
+ when(databaseConnectionManager.getConnections(any(), nullable(String.class), anyInt(), anyInt(), any(ConnectionMode.class))).thenReturn(Collections.singletonList(connection));
+ when(firstPreparedStatement.getConnection()).thenReturn(connection);
+ when(secondPreparedStatement.getConnection()).thenReturn(connection);
+ when(backendStatement.createStorageResource(any(ExecutionUnit.class), eq(connection), anyInt(), any(ConnectionMode.class), any(StatementOption.class), nullable(DatabaseType.class)))
+ .thenReturn(firstPreparedStatement, secondPreparedStatement);
+ ContextManager contextManager = mockContextManager("ds_0", "ds_1");
+ when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+ FirebirdServerPreparedStatement firebirdPreparedStatement = new FirebirdServerPreparedStatement("INSERT INTO t (id, col) VALUES (?, ?)",
+ mockInsertStatementContext(), new HintValueContext());
+ List<List<Object>> parameterSets = Arrays.asList(Arrays.asList(1, "foo_1"), Arrays.asList(2, "foo_2"), Arrays.asList(3, "foo_3"));
+ ExecutionUnit firstExecutionUnit = new ExecutionUnit("ds_0", new SQLUnit("INSERT INTO t_0 (id, col) VALUES (?, ?)", parameterSets.get(0)));
+ ExecutionUnit secondExecutionUnit = new ExecutionUnit("ds_1", new SQLUnit("INSERT INTO t_1 (id, col) VALUES (?, ?)", parameterSets.get(1)));
+ ExecutionUnit thirdExecutionUnit = new ExecutionUnit("ds_0", new SQLUnit("INSERT INTO t_0 (id, col) VALUES (?, ?)", parameterSets.get(2)));
+ try (
+ MockedConstruction<KernelProcessor> ignored = mockConstruction(KernelProcessor.class,
+ (mock, context) -> when(mock.generateExecutionContext(any(), any(), any()))
+ .thenReturn(new ExecutionContext(null, Collections.singletonList(firstExecutionUnit), mock(RouteContext.class)),
+ new ExecutionContext(null, Collections.singletonList(secondExecutionUnit), mock(RouteContext.class)),
+ new ExecutionContext(null, Collections.singletonList(thirdExecutionUnit), mock(RouteContext.class))))) {
+ return new FirebirdBatchedStatementsExecutor(mockConnectionSession(), firebirdPreparedStatement, parameterSets, false).executeBatch();
+ }
+ }
+
+ private BatchUpdateException createFailure(final int[] updateCounts) {
+ return new BatchUpdateException("violation of PRIMARY or UNIQUE KEY constraint", "23000", 335544665, updateCounts);
+ }
+
+ private PreparedStatement mockSameRoutePreparedStatement() throws SQLException {
+ Connection connection = mock(Connection.class, RETURNS_DEEP_STUBS);
+ when(connection.getMetaData().getURL()).thenReturn("jdbc:firebirdsql://127.0.0.1/db");
+ when(databaseConnectionManager.getConnections(any(), nullable(String.class), anyInt(), anyInt(), any(ConnectionMode.class))).thenReturn(Collections.singletonList(connection));
+ PreparedStatement result = mock(PreparedStatement.class);
+ when(result.getConnection()).thenReturn(connection);
+ when(backendStatement.createStorageResource(any(ExecutionUnit.class), eq(connection), anyInt(), any(ConnectionMode.class), any(StatementOption.class), nullable(DatabaseType.class)))
+ .thenReturn(result);
+ return result;
+ }
+
+ private FirebirdBatchCompletion executeSameRouteBatch(final PreparedStatement preparedStatement, final boolean multiError) throws SQLException {
+ ContextManager contextManager = mockContextManager();
+ when(ProxyContext.getInstance().getContextManager()).thenReturn(contextManager);
+ FirebirdServerPreparedStatement firebirdPreparedStatement = new FirebirdServerPreparedStatement("INSERT INTO t (id, col) VALUES (?, ?)",
+ mockInsertStatementContext(), new HintValueContext());
+ List<List<Object>> parameterSets = Arrays.asList(Arrays.asList(1, "foo_1"), Arrays.asList(2, "foo_2"), Arrays.asList(3, "foo_3"));
+ return new FirebirdBatchedStatementsExecutor(mockConnectionSession(), firebirdPreparedStatement, parameterSets, multiError).executeBatch();
+ }
+
private InsertStatementContext mockInsertStatementContext() {
InsertStatement insertStatement = InsertStatement.builder()
.databaseType(databaseType)
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/execute/FirebirdExecuteStatementCommandExecutorTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/execute/FirebirdExecuteStatementCommandExecutorTest.java
index e4442c1a394..fcf327cd9c5 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/execute/FirebirdExecuteStatementCommandExecutorTest.java
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/execute/FirebirdExecuteStatementCommandExecutorTest.java
@@ -20,6 +20,8 @@ package org.apache.shardingsphere.proxy.frontend.firebird.command.query.statemen
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidStatementHandleException;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidTransactionHandleException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.FirebirdBinaryColumnType;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.statement.execute.FirebirdExecuteStatementPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.statement.execute.protocol.FirebirdBlobBinaryProtocolValue;
@@ -53,6 +55,7 @@ import org.apache.shardingsphere.proxy.frontend.firebird.command.query.FirebirdS
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.blob.cache.FirebirdBlobWriteCache;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement.FirebirdStatementResourceCleaner;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement.fetch.FirebirdFetchStatementCache;
+import org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction.FirebirdTransactionIdGenerator;
import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.SelectStatement;
import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.UpdateStatement;
import org.apache.shardingsphere.test.infra.framework.extension.mock.AutoMockExtension;
@@ -83,6 +86,7 @@ import static org.hamcrest.Matchers.isA;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
@@ -122,12 +126,17 @@ class FirebirdExecuteStatementCommandExecutorTest {
private FirebirdExecuteStatementCommandExecutor executor;
+ private int transactionId;
+
@BeforeEach
void setUp() {
FirebirdFetchStatementCache.getInstance().registerConnection(CONNECTION_ID);
FirebirdBlobWriteCache.getInstance().registerConnection(CONNECTION_ID);
+ FirebirdTransactionIdGenerator.getInstance().registerConnection(CONNECTION_ID);
+ transactionId = FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID);
when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
when(packet.getStatementId()).thenReturn(STATEMENT_ID);
+ when(packet.getTransactionId()).thenReturn(transactionId);
ServerPreparedStatementRegistry registry = new ServerPreparedStatementRegistry();
when(connectionSession.getServerPreparedStatementRegistry()).thenReturn(registry);
when(connectionSession.getConnectionContext()).thenReturn(connectionContext);
@@ -145,6 +154,24 @@ class FirebirdExecuteStatementCommandExecutorTest {
FirebirdFetchStatementCache.getInstance().unregisterConnection(CONNECTION_ID);
FirebirdBlobWriteCache.getInstance().unregisterConnection(CONNECTION_ID);
FirebirdBlobBinaryProtocolValue.unregisterConnection(CONNECTION_ID);
+ FirebirdTransactionIdGenerator.getInstance().unregisterConnection(CONNECTION_ID);
+ }
+
+ @Test
+ void assertExecuteWithUnknownStatementId() {
+ when(packet.getStatementId()).thenReturn(99);
+ executor = new FirebirdExecuteStatementCommandExecutor(packet, connectionSession);
+ assertThrows(InvalidStatementHandleException.class, executor::execute);
+ }
+
+ @Test
+ void assertExecuteWithClosedTransactionHandle() {
+ when(packet.getStatementId()).thenReturn(2);
+ FirebirdTransactionIdGenerator.getInstance().closeTransaction(CONNECTION_ID, transactionId);
+ FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID);
+ executor = new FirebirdExecuteStatementCommandExecutor(packet, connectionSession);
+ assertThrows(InvalidTransactionHandleException.class, executor::execute);
+ verify(connectionSession).finishPreparedStatementCache();
}
@Test
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutorTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutorTest.java
index e3b00ed4582..3afc0419604 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutorTest.java
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/statement/prepare/FirebirdPrepareStatementCommandExecutorTest.java
@@ -19,6 +19,7 @@ package org.apache.shardingsphere.proxy.frontend.firebird.command.query.statemen
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import org.apache.shardingsphere.database.connector.firebird.metadata.data.FirebirdBlobInfoRegistry;
+import org.apache.shardingsphere.database.exception.core.exception.syntax.database.NoDatabaseSelectedException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.FirebirdBinaryColumnType;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.info.type.sql.FirebirdSQLInfoPacketType;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.info.type.sql.FirebirdSQLInfoReturnValue;
@@ -54,6 +55,7 @@ import org.apache.shardingsphere.proxy.frontend.firebird.command.query.FirebirdS
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement.FirebirdStatementResourceCleaner;
import org.apache.shardingsphere.proxy.frontend.firebird.command.query.statement.fetch.FirebirdFetchStatementCache;
import org.apache.shardingsphere.sql.parser.engine.api.CacheOption;
+import org.apache.shardingsphere.sql.parser.statement.core.statement.type.ddl.database.DropDatabaseStatement;
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;
@@ -75,6 +77,7 @@ import java.util.Properties;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.isA;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -155,6 +158,27 @@ class FirebirdPrepareStatementCommandExecutorTest {
assertThat(preparedStatement.getSqlStatementContext(), isA(SelectStatementContext.class));
}
+ @Test
+ void assertExecuteDropDatabaseWithoutNameResolvesCurrentDatabase() throws Exception {
+ when(packet.getSQL()).thenReturn("DROP DATABASE");
+ when(connectionSession.getUsedDatabaseName()).thenReturn("foo_db");
+ FirebirdPrepareStatementCommandExecutor executor = new FirebirdPrepareStatementCommandExecutor(packet, connectionSession);
+ Collection<DatabasePacket> actual = executor.execute();
+ FirebirdGenericResponsePacket responsePacket = (FirebirdGenericResponsePacket) actual.iterator().next();
+ FirebirdPrepareStatementReturnPacket returnPacket = (FirebirdPrepareStatementReturnPacket) responsePacket.getData();
+ assertThat(returnPacket.getType(), is(FirebirdSQLInfoReturnValue.DDL));
+ FirebirdServerPreparedStatement preparedStatement = connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(1);
+ assertThat(((DropDatabaseStatement) preparedStatement.getSqlStatementContext().getSqlStatement()).getDatabaseName(), is("foo_db"));
+ }
+
+ @Test
+ void assertExecuteDropDatabaseWithoutNameAndCurrentDatabase() {
+ when(packet.getSQL()).thenReturn("DROP DATABASE");
+ when(connectionSession.getUsedDatabaseName()).thenReturn(null);
+ FirebirdPrepareStatementCommandExecutor executor = new FirebirdPrepareStatementCommandExecutor(packet, connectionSession);
+ assertThrows(NoDatabaseSelectedException.class, executor::execute);
+ }
+
@Test
void assertDescribeCountReturnsBigintType() throws Exception {
when(packet.getSQL()).thenReturn("SELECT COUNT(*) FROM foo_tbl");
@@ -196,6 +220,29 @@ class FirebirdPrepareStatementCommandExecutorTest {
verify(payload).writeInt4LE(FirebirdBinaryColumnType.BLOB.getSubtype());
}
+ @Test
+ void assertDescribeDerivedTableSelect() throws Exception {
+ when(packet.getSQL()).thenReturn("SELECT * FROM (SELECT id FROM foo_tbl) derived_tbl");
+ when(packet.nextItem()).thenReturn(true, true, true, true, true, false);
+ when(packet.getCurrentItem()).thenReturn(
+ FirebirdSQLInfoPacketType.STMT_TYPE,
+ FirebirdSQLInfoPacketType.SELECT,
+ FirebirdSQLInfoPacketType.TYPE,
+ FirebirdSQLInfoPacketType.TYPE,
+ FirebirdSQLInfoPacketType.DESCRIBE_END,
+ FirebirdSQLInfoPacketType.DESCRIBE_END);
+ FirebirdPrepareStatementCommandExecutor executor = new FirebirdPrepareStatementCommandExecutor(packet, connectionSession);
+ Collection<DatabasePacket> actual = executor.execute();
+ FirebirdGenericResponsePacket responsePacket = (FirebirdGenericResponsePacket) actual.iterator().next();
+ FirebirdPrepareStatementReturnPacket returnPacket = (FirebirdPrepareStatementReturnPacket) responsePacket.getData();
+ assertThat(returnPacket.getType(), is(FirebirdSQLInfoReturnValue.SELECT));
+ assertThat(returnPacket.getDescribeSelect().size(), is(1));
+ FirebirdPacketPayload payload = mock(FirebirdPacketPayload.class, Mockito.RETURNS_DEEP_STUBS);
+ FirebirdReturnColumnPacket columnPacket = returnPacket.getDescribeSelect().get(0);
+ columnPacket.write(payload);
+ verify(payload).writeInt4LE(FirebirdBinaryColumnType.LONG.getValue() + 1);
+ }
+
private FirebirdReturnColumnPacket describeSingleColumn(final String sql) throws Exception {
when(packet.getSQL()).thenReturn(sql);
when(packet.nextItem()).thenReturn(true, true, true, true, true, true, false);
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdCommitTransactionCommandExecutorTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdCommitTransactionCommandExecutorTest.java
index a1008d3bde7..a49cdb6f3f2 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdCommitTransactionCommandExecutorTest.java
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdCommitTransactionCommandExecutorTest.java
@@ -17,11 +17,14 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidTransactionHandleException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.transaction.FirebirdCommitTransactionPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
import org.apache.shardingsphere.proxy.backend.connector.jdbc.transaction.ProxyBackendTransactionManager;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
@@ -32,6 +35,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import java.sql.SQLException;
import java.util.Collection;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.isA;
@@ -42,14 +46,29 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class FirebirdCommitTransactionCommandExecutorTest {
+ private static final int CONNECTION_ID = 201;
+
@Mock
private FirebirdCommitTransactionPacket packet;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private ConnectionSession connectionSession;
+ @BeforeEach
+ void setUp() {
+ FirebirdTransactionIdGenerator.getInstance().registerConnection(CONNECTION_ID);
+ FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID);
+ when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
+ }
+
+ @AfterEach
+ void tearDown() {
+ FirebirdTransactionIdGenerator.getInstance().unregisterConnection(CONNECTION_ID);
+ }
+
@Test
void assertExecute() throws SQLException {
+ when(packet.getTransactionId()).thenReturn(1);
try (MockedConstruction<ProxyBackendTransactionManager> mocked = mockConstruction(ProxyBackendTransactionManager.class, (mock, context) -> {
})) {
Collection<DatabasePacket> actual = new FirebirdCommitTransactionCommandExecutor(packet, connectionSession).execute();
@@ -60,6 +79,7 @@ class FirebirdCommitTransactionCommandExecutorTest {
@Test
void assertExecuteWithAutoCommit() throws SQLException {
+ when(packet.getTransactionId()).thenReturn(1);
when(connectionSession.isAutoCommit()).thenReturn(true);
try (MockedConstruction<ProxyBackendTransactionManager> mocked = mockConstruction(ProxyBackendTransactionManager.class)) {
Collection<DatabasePacket> actual = new FirebirdCommitTransactionCommandExecutor(packet, connectionSession).execute();
@@ -67,4 +87,35 @@ class FirebirdCommitTransactionCommandExecutorTest {
assertTrue(mocked.constructed().isEmpty());
}
}
+
+ @Test
+ void assertExecuteWithUnknownTransactionHandle() {
+ when(packet.getTransactionId()).thenReturn(99);
+ try (MockedConstruction<ProxyBackendTransactionManager> mocked = mockConstruction(ProxyBackendTransactionManager.class)) {
+ assertThrows(InvalidTransactionHandleException.class, () -> new FirebirdCommitTransactionCommandExecutor(packet, connectionSession).execute());
+ assertTrue(mocked.constructed().isEmpty());
+ }
+ }
+
+ @Test
+ void assertExecuteTwiceWithSameTransactionHandle() throws SQLException {
+ when(packet.getTransactionId()).thenReturn(1);
+ try (MockedConstruction<ProxyBackendTransactionManager> mocked = mockConstruction(ProxyBackendTransactionManager.class)) {
+ new FirebirdCommitTransactionCommandExecutor(packet, connectionSession).execute();
+ assertThrows(InvalidTransactionHandleException.class, () -> new FirebirdCommitTransactionCommandExecutor(packet, connectionSession).execute());
+ }
+ }
+
+ @Test
+ void assertExecuteWithCommittedTransactionHandleAfterNewTransaction() throws SQLException {
+ when(packet.getTransactionId()).thenReturn(1);
+ try (MockedConstruction<ProxyBackendTransactionManager> mocked = mockConstruction(ProxyBackendTransactionManager.class)) {
+ new FirebirdCommitTransactionCommandExecutor(packet, connectionSession).execute();
+ FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID);
+ assertThrows(InvalidTransactionHandleException.class, () -> new FirebirdCommitTransactionCommandExecutor(packet, connectionSession).execute());
+ when(packet.getTransactionId()).thenReturn(2);
+ Collection<DatabasePacket> actual = new FirebirdCommitTransactionCommandExecutor(packet, connectionSession).execute();
+ assertThat(actual.iterator().next(), isA(FirebirdGenericResponsePacket.class));
+ }
+ }
}
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdRollbackTransactionCommandExecutorTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdRollbackTransactionCommandExecutorTest.java
index 95e106720f1..f8d2f376258 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdRollbackTransactionCommandExecutorTest.java
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdRollbackTransactionCommandExecutorTest.java
@@ -17,11 +17,14 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.InvalidTransactionHandleException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.transaction.FirebirdRollbackTransactionPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
import org.apache.shardingsphere.proxy.backend.connector.jdbc.transaction.ProxyBackendTransactionManager;
import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
@@ -32,6 +35,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import java.sql.SQLException;
import java.util.Collection;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.isA;
@@ -42,14 +46,29 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class FirebirdRollbackTransactionCommandExecutorTest {
+ private static final int CONNECTION_ID = 202;
+
@Mock
private FirebirdRollbackTransactionPacket packet;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private ConnectionSession connectionSession;
+ @BeforeEach
+ void setUp() {
+ FirebirdTransactionIdGenerator.getInstance().registerConnection(CONNECTION_ID);
+ FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID);
+ when(connectionSession.getConnectionId()).thenReturn(CONNECTION_ID);
+ }
+
+ @AfterEach
+ void tearDown() {
+ FirebirdTransactionIdGenerator.getInstance().unregisterConnection(CONNECTION_ID);
+ }
+
@Test
void assertExecute() throws SQLException {
+ when(packet.getTransactionId()).thenReturn(1);
try (MockedConstruction<ProxyBackendTransactionManager> mocked = mockConstruction(ProxyBackendTransactionManager.class, (mock, context) -> {
})) {
Collection<DatabasePacket> actual = new FirebirdRollbackTransactionCommandExecutor(packet, connectionSession).execute();
@@ -60,6 +79,7 @@ class FirebirdRollbackTransactionCommandExecutorTest {
@Test
void assertExecuteWithAutoCommit() throws SQLException {
+ when(packet.getTransactionId()).thenReturn(1);
when(connectionSession.isAutoCommit()).thenReturn(true);
try (MockedConstruction<ProxyBackendTransactionManager> mocked = mockConstruction(ProxyBackendTransactionManager.class)) {
Collection<DatabasePacket> actual = new FirebirdRollbackTransactionCommandExecutor(packet, connectionSession).execute();
@@ -67,4 +87,35 @@ class FirebirdRollbackTransactionCommandExecutorTest {
assertTrue(mocked.constructed().isEmpty());
}
}
+
+ @Test
+ void assertExecuteWithUnknownTransactionHandle() {
+ when(packet.getTransactionId()).thenReturn(99);
+ try (MockedConstruction<ProxyBackendTransactionManager> mocked = mockConstruction(ProxyBackendTransactionManager.class)) {
+ assertThrows(InvalidTransactionHandleException.class, () -> new FirebirdRollbackTransactionCommandExecutor(packet, connectionSession).execute());
+ assertTrue(mocked.constructed().isEmpty());
+ }
+ }
+
+ @Test
+ void assertExecuteTwiceWithSameTransactionHandle() throws SQLException {
+ when(packet.getTransactionId()).thenReturn(1);
+ try (MockedConstruction<ProxyBackendTransactionManager> mocked = mockConstruction(ProxyBackendTransactionManager.class)) {
+ new FirebirdRollbackTransactionCommandExecutor(packet, connectionSession).execute();
+ assertThrows(InvalidTransactionHandleException.class, () -> new FirebirdRollbackTransactionCommandExecutor(packet, connectionSession).execute());
+ }
+ }
+
+ @Test
+ void assertExecuteWithRolledBackTransactionHandleAfterNewTransaction() throws SQLException {
+ when(packet.getTransactionId()).thenReturn(1);
+ try (MockedConstruction<ProxyBackendTransactionManager> mocked = mockConstruction(ProxyBackendTransactionManager.class)) {
+ new FirebirdRollbackTransactionCommandExecutor(packet, connectionSession).execute();
+ FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID);
+ assertThrows(InvalidTransactionHandleException.class, () -> new FirebirdRollbackTransactionCommandExecutor(packet, connectionSession).execute());
+ when(packet.getTransactionId()).thenReturn(2);
+ Collection<DatabasePacket> actual = new FirebirdRollbackTransactionCommandExecutor(packet, connectionSession).execute();
+ assertThat(actual.iterator().next(), isA(FirebirdGenericResponsePacket.class));
+ }
+ }
}
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdStartTransactionCommandExecutorTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdStartTransactionCommandExecutorTest.java
index 185ce9eda83..53cce2c0458 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdStartTransactionCommandExecutorTest.java
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdStartTransactionCommandExecutorTest.java
@@ -17,6 +17,7 @@
package org.apache.shardingsphere.proxy.frontend.firebird.command.query.transaction;
+import org.apache.shardingsphere.database.exception.firebird.exception.protocol.ExcessTransactionsException;
import org.apache.shardingsphere.database.protocol.firebird.packet.command.query.transaction.FirebirdStartTransactionPacket;
import org.apache.shardingsphere.database.protocol.firebird.packet.generic.FirebirdGenericResponsePacket;
import org.apache.shardingsphere.database.protocol.packet.DatabasePacket;
@@ -35,6 +36,8 @@ import java.util.Collection;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.isA;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -74,4 +77,24 @@ class FirebirdStartTransactionCommandExecutorTest {
verify(connectionSession).setReadOnly(true);
verify(connectionSession).setIsolationLevel(TransactionIsolationLevel.SERIALIZABLE);
}
+
+ @Test
+ void assertExecuteWithActiveTransactionExpectsExcessTransactions() {
+ FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID);
+ FirebirdStartTransactionCommandExecutor executor = new FirebirdStartTransactionCommandExecutor(packet, connectionSession);
+ assertThrows(ExcessTransactionsException.class, executor::execute);
+ assertFalse(FirebirdTransactionIdGenerator.getInstance().isTransactionActive(CONNECTION_ID, 2));
+ }
+
+ @Test
+ void assertExecuteAfterClosedTransactionExpectsNextHandle() {
+ int firstTransactionId = FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID);
+ FirebirdTransactionIdGenerator.getInstance().closeTransaction(CONNECTION_ID, firstTransactionId);
+ when(packet.isAutoCommit()).thenReturn(true);
+ when(packet.isReadOnly()).thenReturn(false);
+ when(packet.getIsolationLevel()).thenReturn(TransactionIsolationLevel.SERIALIZABLE);
+ FirebirdStartTransactionCommandExecutor executor = new FirebirdStartTransactionCommandExecutor(packet, connectionSession);
+ Collection<DatabasePacket> actual = executor.execute();
+ assertThat(((FirebirdGenericResponsePacket) actual.iterator().next()).getHandle(), is(firstTransactionId + 1));
+ }
}
diff --git a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdTransactionIdGeneratorTest.java b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdTransactionIdGeneratorTest.java
index 9ea7c393165..ebb66e35e24 100644
--- a/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdTransactionIdGeneratorTest.java
+++ b/proxy/frontend/dialect/firebird/src/test/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/transaction/FirebirdTransactionIdGeneratorTest.java
@@ -23,7 +23,9 @@ import org.junit.jupiter.api.Test;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
class FirebirdTransactionIdGeneratorTest {
@@ -55,4 +57,27 @@ class FirebirdTransactionIdGeneratorTest {
FirebirdTransactionIdGenerator.getInstance().unregisterConnection(CONNECTION_ID);
assertThrows(IllegalStateException.class, () -> FirebirdTransactionIdGenerator.getInstance().getTransactionId(CONNECTION_ID));
}
+
+ @Test
+ void assertIsTransactionActiveWithClosedTransaction() {
+ int transactionId = FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID);
+ assertTrue(FirebirdTransactionIdGenerator.getInstance().isTransactionActive(CONNECTION_ID, transactionId));
+ FirebirdTransactionIdGenerator.getInstance().closeTransaction(CONNECTION_ID, transactionId);
+ assertFalse(FirebirdTransactionIdGenerator.getInstance().isTransactionActive(CONNECTION_ID, transactionId));
+ }
+
+ @Test
+ void assertIsTransactionActiveWithoutRegisteredConnection() {
+ FirebirdTransactionIdGenerator.getInstance().unregisterConnection(CONNECTION_ID);
+ assertThrows(IllegalStateException.class, () -> FirebirdTransactionIdGenerator.getInstance().isTransactionActive(CONNECTION_ID, 1));
+ }
+
+ @Test
+ void assertHasActiveTransaction() {
+ assertFalse(FirebirdTransactionIdGenerator.getInstance().hasActiveTransaction(CONNECTION_ID));
+ int transactionId = FirebirdTransactionIdGenerator.getInstance().nextTransactionId(CONNECTION_ID);
+ assertTrue(FirebirdTransactionIdGenerator.getInstance().hasActiveTransaction(CONNECTION_ID));
+ FirebirdTransactionIdGenerator.getInstance().closeTransaction(CONNECTION_ID, transactionId);
+ assertFalse(FirebirdTransactionIdGenerator.getInstance().hasActiveTransaction(CONNECTION_ID));
+ }
}
diff --git a/test/it/parser/src/main/resources/case/ddl/create-database.xml b/test/it/parser/src/main/resources/case/ddl/create-database.xml
index 777ccb6b533..359322707c1 100644
--- a/test/it/parser/src/main/resources/case/ddl/create-database.xml
+++ b/test/it/parser/src/main/resources/case/ddl/create-database.xml
@@ -97,4 +97,8 @@
<create-database sql-case-id="create_database_doris_if_not_exists_with_properties">
<property start-index="52" stop-index="91" key="storage_vault_name" value="hdfs_demo_vault" />
</create-database>
+ <create-database sql-case-id="create_database_with_filespec" />
+ <create-database sql-case-id="create_schema_with_filespec" />
+ <create-database sql-case-id="create_database_with_remote_filespec" />
+ <create-database sql-case-id="create_database_with_inet_filespec" />
</sql-parser-test-cases>
diff --git a/test/it/parser/src/main/resources/case/dml/select-combine.xml b/test/it/parser/src/main/resources/case/dml/select-combine.xml
index e1a69aa9071..5bf1571bf8a 100644
--- a/test/it/parser/src/main/resources/case/dml/select-combine.xml
+++ b/test/it/parser/src/main/resources/case/dml/select-combine.xml
@@ -71,6 +71,118 @@
</combine>
</select>
+ <select sql-case-id="select_union_firebird">
+ <projections start-index="7" stop-index="10">
+ <column-projection name="COL1" start-index="7" stop-index="10" />
+ </projections>
+ <from>
+ <simple-table name="TEST_BATCH_0" start-index="17" stop-index="28" />
+ </from>
+ <combine combine-type="UNION" start-index="0" stop-index="64">
+ <left>
+ <projections start-index="7" stop-index="10">
+ <column-projection name="COL1" start-index="7" stop-index="10" />
+ </projections>
+ <from>
+ <simple-table name="TEST_BATCH_0" start-index="17" stop-index="28" />
+ </from>
+ </left>
+ <right>
+ <projections start-index="43" stop-index="46">
+ <column-projection name="COL1" start-index="43" stop-index="46" />
+ </projections>
+ <from>
+ <simple-table name="TEST_BATCH_0" start-index="53" stop-index="64" />
+ </from>
+ </right>
+ </combine>
+ </select>
+
+ <select sql-case-id="select_union_all_firebird">
+ <projections start-index="7" stop-index="10">
+ <column-projection name="COL1" start-index="7" stop-index="10" />
+ </projections>
+ <from>
+ <simple-table name="TEST_BATCH_0" start-index="17" stop-index="28" />
+ </from>
+ <combine combine-type="UNION_ALL" start-index="0" stop-index="68">
+ <left>
+ <projections start-index="7" stop-index="10">
+ <column-projection name="COL1" start-index="7" stop-index="10" />
+ </projections>
+ <from>
+ <simple-table name="TEST_BATCH_0" start-index="17" stop-index="28" />
+ </from>
+ </left>
+ <right>
+ <projections start-index="47" stop-index="50">
+ <column-projection name="COL1" start-index="47" stop-index="50" />
+ </projections>
+ <from>
+ <simple-table name="TEST_BATCH_0" start-index="57" stop-index="68" />
+ </from>
+ </right>
+ </combine>
+ </select>
+
+ <select sql-case-id="select_union_with_comment_firebird">
+ <projections start-index="7" stop-index="10">
+ <column-projection name="COL1" start-index="7" stop-index="10" />
+ </projections>
+ <from>
+ <simple-table name="TEST_BATCH_0" start-index="17" stop-index="28" />
+ </from>
+ <combine combine-type="UNION" start-index="0" stop-index="74">
+ <left>
+ <projections start-index="7" stop-index="10">
+ <column-projection name="COL1" start-index="7" stop-index="10" />
+ </projections>
+ <from>
+ <simple-table name="TEST_BATCH_0" start-index="17" stop-index="28" />
+ </from>
+ </left>
+ <right>
+ <projections start-index="53" stop-index="56">
+ <column-projection name="COL1" start-index="53" stop-index="56" />
+ </projections>
+ <from>
+ <simple-table name="TEST_BATCH_0" start-index="63" stop-index="74" />
+ </from>
+ </right>
+ </combine>
+ <comment start-index="36" stop-index="44" text="/* ALL */" />
+ </select>
+
+ <select sql-case-id="select_union_order_by_firebird">
+ <projections start-index="7" stop-index="10">
+ <column-projection name="COL1" start-index="7" stop-index="10" />
+ </projections>
+ <from>
+ <simple-table name="TEST_BATCH_0" start-index="17" stop-index="28" />
+ </from>
+ <combine combine-type="UNION" start-index="0" stop-index="64">
+ <left>
+ <projections start-index="7" stop-index="10">
+ <column-projection name="COL1" start-index="7" stop-index="10" />
+ </projections>
+ <from>
+ <simple-table name="TEST_BATCH_0" start-index="17" stop-index="28" />
+ </from>
+ </left>
+ <right>
+ <projections start-index="43" stop-index="46">
+ <column-projection name="COL1" start-index="43" stop-index="46" />
+ </projections>
+ <from>
+ <simple-table name="TEST_BATCH_0" start-index="53" stop-index="64" />
+ </from>
+ </right>
+ </combine>
+ <order-by>
+ <index-item index="1" start-index="75" stop-index="75" />
+ </order-by>
+ </select>
+
<select sql-case-id="select_union_doris">
<projections start-index="7" stop-index="7">
<expression-projection text="1" start-index="7" stop-index="7">
diff --git a/test/it/parser/src/main/resources/case/dml/select.xml b/test/it/parser/src/main/resources/case/dml/select.xml
index b3f4a73209f..5b70bcb6e5d 100644
--- a/test/it/parser/src/main/resources/case/dml/select.xml
+++ b/test/it/parser/src/main/resources/case/dml/select.xml
@@ -13492,16 +13492,6 @@
</from>
</select>
- <select sql-case-id="select_union_firebird">
- <projections start-index="7" stop-index="7">
- <expression-projection text="1" start-index="7" stop-index="7">
- <expr>
- <literal-expression value="1" start-index="7" stop-index="7" />
- </expr>
- </expression-projection>
- </projections>
- </select>
-
<select sql-case-id="select_subquery_table_firebird">
<projections start-index="7" stop-index="7">
<shorthand-projection start-index="7" stop-index="7" />
diff --git a/test/it/parser/src/main/resources/sql/supported/ddl/create-database.xml b/test/it/parser/src/main/resources/sql/supported/ddl/create-database.xml
index ba65d78c32b..67f68529f6e 100644
--- a/test/it/parser/src/main/resources/sql/supported/ddl/create-database.xml
+++ b/test/it/parser/src/main/resources/sql/supported/ddl/create-database.xml
@@ -158,4 +158,8 @@
<sql-case id="create_remote_schema_with_all_options" value="CREATE REMOTE SCHEMA lusiadas COMMENT 'Remote test schema' LOCATION '/lusiadas.db/external' MANAGEDLOCATION '/lusiadas.db/managed' WITH DBPROPERTIES ('creator'='Claire', 'create_date'='2025-07-09');" db-types="Hive" />
<sql-case id="create_database_doris_with_properties" value="CREATE DATABASE `db_test` PROPERTIES ("storage_vault_name" = "hdfs_demo_vault")" db-types="Doris" />
<sql-case id="create_database_doris_if_not_exists_with_properties" value="CREATE DATABASE IF NOT EXISTS `db_test` PROPERTIES ("storage_vault_name" = "hdfs_demo_vault")" db-types="Doris" />
+ <sql-case id="create_database_with_filespec" value="CREATE DATABASE 'C:\dbs\sales.fdb'" db-types="Firebird" />
+ <sql-case id="create_schema_with_filespec" value="CREATE SCHEMA '/opt/dbs/sales.fdb'" db-types="Firebird" />
+ <sql-case id="create_database_with_remote_filespec" value="CREATE DATABASE 'baseserver:test'" db-types="Firebird" />
+ <sql-case id="create_database_with_inet_filespec" value="CREATE DATABASE 'inet://baseserver:3050/dbs/sales.fdb'" db-types="Firebird" />
</sql-cases>
diff --git a/test/it/parser/src/main/resources/sql/supported/ddl/drop-database.xml b/test/it/parser/src/main/resources/sql/supported/ddl/drop-database.xml
index 99fa58d9373..35437256701 100644
--- a/test/it/parser/src/main/resources/sql/supported/ddl/drop-database.xml
+++ b/test/it/parser/src/main/resources/sql/supported/ddl/drop-database.xml
@@ -19,7 +19,7 @@
<sql-cases>
<sql-case id="drop_database_if_exist" value="DROP DATABASE IF EXISTS database1" db-types="PostgreSQL,openGauss,SQLServer,Doris" />
<sql-case id="drop_database" value="DROP DATABASE database1" db-types="PostgreSQL,openGauss,SQLServer,MySQL" />
- <sql-case id="drop_database_without_database_name" value="DROP DATABASE" db-types="Oracle" />
+ <sql-case id="drop_database_without_database_name" value="DROP DATABASE" db-types="Oracle,Firebird" />
<sql-case id="drop_database_including_backups_noprompt" value="DROP DATABASE INCLUDING BACKUPS NOPROMPT;" db-types="Oracle" />
<sql-case id="drop_database_simple" value="DROP DATABASE test_db" db-types="Hive" />
<sql-case id="drop_database_if_exists" value="DROP DATABASE IF EXISTS test_db" db-types="Hive" />
diff --git a/test/it/parser/src/main/resources/sql/supported/dml/select-combine.xml b/test/it/parser/src/main/resources/sql/supported/dml/select-combine.xml
index 794ed652ac7..099969e1f4d 100644
--- a/test/it/parser/src/main/resources/sql/supported/dml/select-combine.xml
+++ b/test/it/parser/src/main/resources/sql/supported/dml/select-combine.xml
@@ -19,6 +19,10 @@
<sql-cases>
<sql-case id="select_with_except" value="(SELECT * FROM t1) EXCEPT (SELECT * FROM t2)" db-types="MySQL,Doris" />
<sql-case id="select_union" value="SELECT * FROM table1 UNION SELECT * FROM table2" db-types="MySQL,PostgreSQL,openGauss,Doris" />
+ <sql-case id="select_union_firebird" value="SELECT COL1 FROM TEST_BATCH_0 UNION SELECT COL1 FROM TEST_BATCH_0" db-types="Firebird" />
+ <sql-case id="select_union_all_firebird" value="SELECT COL1 FROM TEST_BATCH_0 UNION ALL SELECT COL1 FROM TEST_BATCH_0" db-types="Firebird" />
+ <sql-case id="select_union_with_comment_firebird" value="SELECT COL1 FROM TEST_BATCH_0 UNION /* ALL */ SELECT COL1 FROM TEST_BATCH_0" db-types="Firebird" />
+ <sql-case id="select_union_order_by_firebird" value="SELECT COL1 FROM TEST_BATCH_0 UNION SELECT COL1 FROM TEST_BATCH_0 ORDER BY 1" db-types="Firebird" />
<sql-case id="select_union_all" value="SELECT * FROM table1 UNION ALL SELECT * FROM table2" db-types="MySQL,PostgreSQL,openGauss,Doris" />
<sql-case id="select_union_all_order_by" value="SELECT * FROM table1 UNION ALL SELECT * FROM table2 ORDER BY id" db-types="MySQL,PostgreSQL,openGauss" />
<sql-case id="select_union_all_order_by_limit" value="SELECT * FROM table1 UNION ALL SELECT * FROM table2 ORDER BY id LIMIT 1, 1" db-types="MySQL,openGauss" />
diff --git a/test/it/parser/src/main/resources/sql/supported/dml/select.xml b/test/it/parser/src/main/resources/sql/supported/dml/select.xml
index 3b1a8c67ed3..d3417a2048c 100644
--- a/test/it/parser/src/main/resources/sql/supported/dml/select.xml
+++ b/test/it/parser/src/main/resources/sql/supported/dml/select.xml
@@ -453,7 +453,6 @@
<sql-case id="select_cast_literal_firebird" value="SELECT CAST(1 AS VARCHAR(10)) FROM t_order" db-types="Firebird"/>
<sql-case id="select_variable_identifier_firebird" value="SELECT LOCAL.var FROM t_order" db-types="Firebird"/>
<sql-case id="select_comma_join_firebird" value="SELECT * FROM t_order a, t_order_item b" db-types="Firebird"/>
- <sql-case id="select_union_firebird" value="SELECT 1 UNION SELECT 2" db-types="Firebird"/>
<sql-case id="select_subquery_table_firebird" value="SELECT * FROM (SELECT 1) sub" db-types="Firebird"/>
<sql-case id="select_subquery_with_with_firebird" value="SELECT * FROM (WITH t AS (SELECT 1) SELECT * FROM t) sub" db-types="Firebird"/>
<sql-case id="select_not_expression_sql92" value="SELECT NOT (1=1) FROM t_order" db-types="SQL92" />