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 (&quot;storage_vault_name&quot; = &quot;hdfs_demo_vault&quot;)" db-types="Doris" />
     <sql-case id="create_database_doris_if_not_exists_with_properties" value="CREATE DATABASE IF NOT EXISTS `db_test` PROPERTIES (&quot;storage_vault_name&quot; = &quot;hdfs_demo_vault&quot;)" 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" />