From f4bca9358ce26d6a3ea09b0d03c06b766d49813e Mon Sep 17 00:00:00 2001 From: xiaosaxiaozai Date: Thu, 18 Dec 2025 21:34:07 +0800 Subject: [PATCH 1/6] Support for the SHOW TRANSACTION statement and the L2_DISTANCE function --- .../src/main/antlr4/imports/doris/BaseRule.g4 | 5 ++ .../main/antlr4/imports/doris/DALStatement.g4 | 5 ++ .../statement/DorisStatementVisitor.java | 21 ++++- .../type/DorisDALStatementVisitor.java | 14 +++ .../show/MySQLShowTransactionStatement.java | 41 +++++++++ .../parser/jaxb/RootSQLParserTestCases.java | 4 + ...MySQLShowTransactionStatementTestCase.java | 40 +++++++++ .../src/main/resources/case/dal/show.xml | 66 ++++++++++++++ .../case/dml/select-special-function.xml | 88 +++++++++++++++++++ .../main/resources/sql/supported/dal/show.xml | 4 + .../supported/dml/select-special-function.xml | 2 + 11 files changed, 288 insertions(+), 2 deletions(-) create mode 100644 parser/sql/statement/dialect/mysql/src/main/java/org/apache/shardingsphere/sql/parser/statement/mysql/dal/show/MySQLShowTransactionStatement.java create mode 100644 test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/statement/dal/dialect/mysql/show/MySQLShowTransactionStatementTestCase.java diff --git a/parser/sql/engine/dialect/doris/src/main/antlr4/imports/doris/BaseRule.g4 b/parser/sql/engine/dialect/doris/src/main/antlr4/imports/doris/BaseRule.g4 index 81e18359d0749..d6757b9885e62 100644 --- a/parser/sql/engine/dialect/doris/src/main/antlr4/imports/doris/BaseRule.g4 +++ b/parser/sql/engine/dialect/doris/src/main/antlr4/imports/doris/BaseRule.g4 @@ -76,6 +76,7 @@ literals | bitValueLiterals | booleanLiterals | nullValueLiterals + | arrayLiterals ; string_ @@ -110,6 +111,10 @@ nullValueLiterals : NULL ; +arrayLiterals + : LBT_ (expr (COMMA_ expr)*)? RBT_ + ; + collationName : textOrIdentifier | BINARY ; diff --git a/parser/sql/engine/dialect/doris/src/main/antlr4/imports/doris/DALStatement.g4 b/parser/sql/engine/dialect/doris/src/main/antlr4/imports/doris/DALStatement.g4 index 38badae1a1053..73cd6bb5451dc 100644 --- a/parser/sql/engine/dialect/doris/src/main/antlr4/imports/doris/DALStatement.g4 +++ b/parser/sql/engine/dialect/doris/src/main/antlr4/imports/doris/DALStatement.g4 @@ -254,6 +254,10 @@ showCharset : SHOW CHARSET ; +showTransaction + : SHOW TRANSACTION fromDatabase? showWhereClause + ; + setCharacter : SET (CHARACTER SET | CHARSET) (charsetName | DEFAULT) ; @@ -707,4 +711,5 @@ show | showVariables | showReplicas | showReplicaStatus + | showTransaction ; diff --git a/parser/sql/engine/dialect/doris/src/main/java/org/apache/shardingsphere/sql/parser/engine/doris/visitor/statement/DorisStatementVisitor.java b/parser/sql/engine/dialect/doris/src/main/java/org/apache/shardingsphere/sql/parser/engine/doris/visitor/statement/DorisStatementVisitor.java index e4a4f98fa35db..ebcd10180c5ee 100644 --- a/parser/sql/engine/dialect/doris/src/main/java/org/apache/shardingsphere/sql/parser/engine/doris/visitor/statement/DorisStatementVisitor.java +++ b/parser/sql/engine/dialect/doris/src/main/java/org/apache/shardingsphere/sql/parser/engine/doris/visitor/statement/DorisStatementVisitor.java @@ -30,6 +30,7 @@ import org.apache.shardingsphere.sql.parser.autogen.DorisStatementBaseVisitor; import org.apache.shardingsphere.sql.parser.autogen.DorisStatementParser.AggregationFunctionContext; import org.apache.shardingsphere.sql.parser.autogen.DorisStatementParser.AliasContext; +import org.apache.shardingsphere.sql.parser.autogen.DorisStatementParser.ArrayLiteralsContext; import org.apache.shardingsphere.sql.parser.autogen.DorisStatementParser.AssignmentContext; import org.apache.shardingsphere.sql.parser.autogen.DorisStatementParser.AssignmentValueContext; import org.apache.shardingsphere.sql.parser.autogen.DorisStatementParser.AssignmentValuesContext; @@ -290,7 +291,10 @@ public final ASTNode visitLiterals(final LiteralsContext ctx) { if (null != ctx.nullValueLiterals()) { return visit(ctx.nullValueLiterals()); } - throw new IllegalStateException("Literals must have string, number, dateTime, hex, bit, boolean or null."); + if (null != ctx.arrayLiterals()) { + return visit(ctx.arrayLiterals()); + } + throw new IllegalStateException("Literals must have string, number, dateTime, hex, bit, boolean, null or array."); } @Override @@ -336,6 +340,15 @@ public final ASTNode visitNullValueLiterals(final NullValueLiteralsContext ctx) return new NullLiteralValue(ctx.getText()); } + @Override + public final ASTNode visitArrayLiterals(final ArrayLiteralsContext ctx) { + ListExpression result = new ListExpression(ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex()); + for (ExprContext each : ctx.expr()) { + result.getItems().add((ExpressionSegment) visit(each)); + } + return result; + } + @Override public final ASTNode visitIdentifier(final IdentifierContext ctx) { return new IdentifierValue(ctx.getText()); @@ -622,7 +635,11 @@ public final ASTNode visitSimpleExpr(final SimpleExprContext ctx) { return result; } if (null != ctx.literals()) { - return SQLUtils.createLiteralExpression(visit(ctx.literals()), startIndex, stopIndex, ctx.literals().start.getInputStream().getText(new Interval(startIndex, stopIndex))); + ASTNode astNode = visit(ctx.literals()); + if (astNode instanceof ExpressionSegment) { + return astNode; + } + return SQLUtils.createLiteralExpression(astNode, startIndex, stopIndex, ctx.literals().start.getInputStream().getText(new Interval(startIndex, stopIndex))); } if (null != ctx.intervalExpression()) { return visit(ctx.intervalExpression()); diff --git a/parser/sql/engine/dialect/doris/src/main/java/org/apache/shardingsphere/sql/parser/engine/doris/visitor/statement/type/DorisDALStatementVisitor.java b/parser/sql/engine/dialect/doris/src/main/java/org/apache/shardingsphere/sql/parser/engine/doris/visitor/statement/type/DorisDALStatementVisitor.java index 11cb7e4f1ed6b..3e901510a178e 100644 --- a/parser/sql/engine/dialect/doris/src/main/java/org/apache/shardingsphere/sql/parser/engine/doris/visitor/statement/type/DorisDALStatementVisitor.java +++ b/parser/sql/engine/dialect/doris/src/main/java/org/apache/shardingsphere/sql/parser/engine/doris/visitor/statement/type/DorisDALStatementVisitor.java @@ -109,6 +109,7 @@ import org.apache.shardingsphere.sql.parser.autogen.DorisStatementParser.ShowStatusContext; import org.apache.shardingsphere.sql.parser.autogen.DorisStatementParser.ShowTableStatusContext; import org.apache.shardingsphere.sql.parser.autogen.DorisStatementParser.ShowTablesContext; +import org.apache.shardingsphere.sql.parser.autogen.DorisStatementParser.ShowTransactionContext; import org.apache.shardingsphere.sql.parser.autogen.DorisStatementParser.ShowTriggersContext; import org.apache.shardingsphere.sql.parser.autogen.DorisStatementParser.ShowVariablesContext; import org.apache.shardingsphere.sql.parser.autogen.DorisStatementParser.ShowWarningsContext; @@ -232,6 +233,7 @@ import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.process.MySQLShowProcessListStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.profile.MySQLShowProfileStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.profile.MySQLShowProfilesStatement; +import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.MySQLShowTransactionStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.table.MySQLShowCreateTableStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.table.MySQLShowOpenTablesStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.table.MySQLShowTableStatusStatement; @@ -815,6 +817,18 @@ public ASTNode visitShowPrivileges(final ShowPrivilegesContext ctx) { return new MySQLShowPrivilegesStatement(getDatabaseType()); } + @Override + public ASTNode visitShowTransaction(final ShowTransactionContext ctx) { + MySQLShowTransactionStatement result = new MySQLShowTransactionStatement(getDatabaseType()); + if (null != ctx.fromDatabase()) { + result.setFromDatabase(((FromDatabaseSegment) visit(ctx.fromDatabase())).getDatabase()); + } + if (null != ctx.showWhereClause()) { + result.setWhere((WhereSegment) visit(ctx.showWhereClause())); + } + return result; + } + @Override public ASTNode visitShutdown(final ShutdownContext ctx) { return new MySQLShutdownStatement(getDatabaseType()); diff --git a/parser/sql/statement/dialect/mysql/src/main/java/org/apache/shardingsphere/sql/parser/statement/mysql/dal/show/MySQLShowTransactionStatement.java b/parser/sql/statement/dialect/mysql/src/main/java/org/apache/shardingsphere/sql/parser/statement/mysql/dal/show/MySQLShowTransactionStatement.java new file mode 100644 index 0000000000000..4c50735c571ee --- /dev/null +++ b/parser/sql/statement/dialect/mysql/src/main/java/org/apache/shardingsphere/sql/parser/statement/mysql/dal/show/MySQLShowTransactionStatement.java @@ -0,0 +1,41 @@ +/* + * 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.sql.parser.statement.mysql.dal.show; + +import lombok.Getter; +import lombok.Setter; +import org.apache.shardingsphere.database.connector.core.type.DatabaseType; +import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dal.DALStatement; +import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.DatabaseSegment; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.WhereSegment; + +/** + * Show transaction statement for MySQL. + */ +@Getter +@Setter +public final class MySQLShowTransactionStatement extends DALStatement { + + private DatabaseSegment fromDatabase; + + private WhereSegment where; + + public MySQLShowTransactionStatement(final DatabaseType databaseType) { + super(databaseType); + } +} diff --git a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/RootSQLParserTestCases.java b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/RootSQLParserTestCases.java index e76a915e849f9..95a4cc56c4f3e 100644 --- a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/RootSQLParserTestCases.java +++ b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/RootSQLParserTestCases.java @@ -68,6 +68,7 @@ import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.show.trigger.MySQLShowCreateTriggerStatementTestCase; import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.show.trigger.MySQLShowTriggersStatementTestCase; import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.show.variable.MySQLShowVariablesStatementTestCase; +import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.show.MySQLShowTransactionStatementTestCase; import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.table.MySQLCheckTableStatementTestCase; import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.table.MySQLChecksumTableStatementTestCase; import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.table.MySQLOptimizeTableStatementTestCase; @@ -671,6 +672,9 @@ public final class RootSQLParserTestCases { @XmlElement(name = "show-index") private final List showIndexTestCases = new LinkedList<>(); + @XmlElement(name = "show-transaction") + private final List showTransactionTestCases = new LinkedList<>(); + @XmlElement(name = "show") private final List showTestCases = new LinkedList<>(); diff --git a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/statement/dal/dialect/mysql/show/MySQLShowTransactionStatementTestCase.java b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/statement/dal/dialect/mysql/show/MySQLShowTransactionStatementTestCase.java new file mode 100644 index 0000000000000..78a56b08b897e --- /dev/null +++ b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/statement/dal/dialect/mysql/show/MySQLShowTransactionStatementTestCase.java @@ -0,0 +1,40 @@ +/* + * 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.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.show; + +import lombok.Getter; +import lombok.Setter; +import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.SQLParserTestCase; +import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.segment.impl.database.ExpectedDatabase; +import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.segment.impl.where.ExpectedWhereClause; + +import javax.xml.bind.annotation.XmlElement; + +/** + * Show transaction statement test case for MySQL. + */ +@Getter +@Setter +public final class MySQLShowTransactionStatementTestCase extends SQLParserTestCase { + + @XmlElement + private ExpectedDatabase database; + + @XmlElement(name = "where") + private ExpectedWhereClause where; +} diff --git a/test/it/parser/src/main/resources/case/dal/show.xml b/test/it/parser/src/main/resources/case/dal/show.xml index 158f7b6b9a743..7973418b4cd8e 100644 --- a/test/it/parser/src/main/resources/case/dal/show.xml +++ b/test/it/parser/src/main/resources/case/dal/show.xml @@ -1098,4 +1098,70 @@ + + + + + + + + + = + + + + + + + + + + + + + + + + = + + + + + + + + + + + + + + + + + = + + + + + + + + + + + + + + + + + = + + + + + + + diff --git a/test/it/parser/src/main/resources/case/dml/select-special-function.xml b/test/it/parser/src/main/resources/case/dml/select-special-function.xml index 980c53490c4f3..102437193bce8 100644 --- a/test/it/parser/src/main/resources/case/dml/select-special-function.xml +++ b/test/it/parser/src/main/resources/case/dml/select-special-function.xml @@ -5304,4 +5304,92 @@ + + + + diff --git a/test/it/parser/src/main/resources/sql/supported/dal/show.xml b/test/it/parser/src/main/resources/sql/supported/dal/show.xml index 0ebdfa0697bd5..e4f5f91f71f8c 100644 --- a/test/it/parser/src/main/resources/sql/supported/dal/show.xml +++ b/test/it/parser/src/main/resources/sql/supported/dal/show.xml @@ -235,4 +235,8 @@ + + + + diff --git a/test/it/parser/src/main/resources/sql/supported/dml/select-special-function.xml b/test/it/parser/src/main/resources/sql/supported/dml/select-special-function.xml index 3944d08c6e2ee..2105eacefdb10 100644 --- a/test/it/parser/src/main/resources/sql/supported/dml/select-special-function.xml +++ b/test/it/parser/src/main/resources/sql/supported/dml/select-special-function.xml @@ -305,4 +305,6 @@ + + From a0e8fd0957e8740765fe082c62d891dea746386f Mon Sep 17 00:00:00 2001 From: xiaosaxiaozai Date: Fri, 19 Dec 2025 15:51:35 +0800 Subject: [PATCH 2/6] Support for the SHOW TRANSACTION statement and the L2_DISTANCE function --- .../type/DorisDALStatementVisitor.java | 3 +- .../show/MySQLShowTransactionStatement.java | 18 +++++-- .../mysql/MySQLDALStatementAssert.java | 5 ++ .../MySQLShowTransactionStatementAssert.java | 51 +++++++++++++++++++ ...MySQLShowTransactionStatementTestCase.java | 6 +-- .../src/main/resources/case/dal/show.xml | 36 +++++++------ 6 files changed, 96 insertions(+), 23 deletions(-) create mode 100644 test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/statement/dal/dialect/mysql/type/MySQLShowTransactionStatementAssert.java diff --git a/parser/sql/engine/dialect/doris/src/main/java/org/apache/shardingsphere/sql/parser/engine/doris/visitor/statement/type/DorisDALStatementVisitor.java b/parser/sql/engine/dialect/doris/src/main/java/org/apache/shardingsphere/sql/parser/engine/doris/visitor/statement/type/DorisDALStatementVisitor.java index 3e901510a178e..cdfbeeaa03ada 100644 --- a/parser/sql/engine/dialect/doris/src/main/java/org/apache/shardingsphere/sql/parser/engine/doris/visitor/statement/type/DorisDALStatementVisitor.java +++ b/parser/sql/engine/dialect/doris/src/main/java/org/apache/shardingsphere/sql/parser/engine/doris/visitor/statement/type/DorisDALStatementVisitor.java @@ -821,11 +821,12 @@ public ASTNode visitShowPrivileges(final ShowPrivilegesContext ctx) { public ASTNode visitShowTransaction(final ShowTransactionContext ctx) { MySQLShowTransactionStatement result = new MySQLShowTransactionStatement(getDatabaseType()); if (null != ctx.fromDatabase()) { - result.setFromDatabase(((FromDatabaseSegment) visit(ctx.fromDatabase())).getDatabase()); + result.setFromDatabase((FromDatabaseSegment) visit(ctx.fromDatabase())); } if (null != ctx.showWhereClause()) { result.setWhere((WhereSegment) visit(ctx.showWhereClause())); } + result.addParameterMarkers(getParameterMarkerSegments()); return result; } diff --git a/parser/sql/statement/dialect/mysql/src/main/java/org/apache/shardingsphere/sql/parser/statement/mysql/dal/show/MySQLShowTransactionStatement.java b/parser/sql/statement/dialect/mysql/src/main/java/org/apache/shardingsphere/sql/parser/statement/mysql/dal/show/MySQLShowTransactionStatement.java index 4c50735c571ee..abe2c1ae289e8 100644 --- a/parser/sql/statement/dialect/mysql/src/main/java/org/apache/shardingsphere/sql/parser/statement/mysql/dal/show/MySQLShowTransactionStatement.java +++ b/parser/sql/statement/dialect/mysql/src/main/java/org/apache/shardingsphere/sql/parser/statement/mysql/dal/show/MySQLShowTransactionStatement.java @@ -20,9 +20,14 @@ import lombok.Getter; import lombok.Setter; import org.apache.shardingsphere.database.connector.core.type.DatabaseType; -import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dal.DALStatement; -import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.DatabaseSegment; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dal.FromDatabaseSegment; import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.WhereSegment; +import org.apache.shardingsphere.sql.parser.statement.core.statement.attribute.SQLStatementAttributes; +import org.apache.shardingsphere.sql.parser.statement.core.statement.attribute.type.AllowNotUseDatabaseSQLStatementAttribute; +import org.apache.shardingsphere.sql.parser.statement.core.statement.attribute.type.DatabaseSelectRequiredSQLStatementAttribute; +import org.apache.shardingsphere.sql.parser.statement.core.statement.attribute.type.FromDatabaseSQLStatementAttribute; +import org.apache.shardingsphere.sql.parser.statement.core.statement.attribute.type.TablelessDataSourceBroadcastRouteSQLStatementAttribute; +import org.apache.shardingsphere.sql.parser.statement.core.statement.type.dal.DALStatement; /** * Show transaction statement for MySQL. @@ -31,11 +36,18 @@ @Setter public final class MySQLShowTransactionStatement extends DALStatement { - private DatabaseSegment fromDatabase; + private FromDatabaseSegment fromDatabase; private WhereSegment where; public MySQLShowTransactionStatement(final DatabaseType databaseType) { super(databaseType); } + + @Override + public SQLStatementAttributes getAttributes() { + String databaseName = null == fromDatabase ? null : fromDatabase.getDatabase().getIdentifier().getValue(); + return new SQLStatementAttributes(new DatabaseSelectRequiredSQLStatementAttribute(), new FromDatabaseSQLStatementAttribute(fromDatabase), + new TablelessDataSourceBroadcastRouteSQLStatementAttribute(), new AllowNotUseDatabaseSQLStatementAttribute(true, databaseName)); + } } diff --git a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/statement/dal/dialect/mysql/MySQLDALStatementAssert.java b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/statement/dal/dialect/mysql/MySQLDALStatementAssert.java index 5f63a968529a2..11c6c8380fef1 100644 --- a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/statement/dal/dialect/mysql/MySQLDALStatementAssert.java +++ b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/statement/dal/dialect/mysql/MySQLDALStatementAssert.java @@ -47,6 +47,7 @@ import org.apache.shardingsphere.sql.parser.statement.mysql.dal.resource.MySQLCreateResourceGroupStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.resource.MySQLDropResourceGroupStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.resource.MySQLSetResourceGroupStatement; +import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.MySQLShowTransactionStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.character.MySQLShowCollationStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.column.MySQLDescribeStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.column.MySQLShowColumnsStatement; @@ -111,6 +112,7 @@ import org.apache.shardingsphere.test.it.sql.parser.internal.asserts.statement.dal.dialect.mysql.type.MySQLShowStatusStatementAssert; import org.apache.shardingsphere.test.it.sql.parser.internal.asserts.statement.dal.dialect.mysql.type.MySQLShowTableStatusStatementAssert; import org.apache.shardingsphere.test.it.sql.parser.internal.asserts.statement.dal.dialect.mysql.type.MySQLShowTablesStatementAssert; +import org.apache.shardingsphere.test.it.sql.parser.internal.asserts.statement.dal.dialect.mysql.type.MySQLShowTransactionStatementAssert; import org.apache.shardingsphere.test.it.sql.parser.internal.asserts.statement.dal.dialect.mysql.type.MySQLShowTriggersStatementAssert; import org.apache.shardingsphere.test.it.sql.parser.internal.asserts.statement.dal.dialect.mysql.type.MySQLShowVariablesStatementAssert; import org.apache.shardingsphere.test.it.sql.parser.internal.asserts.statement.dal.dialect.mysql.type.MySQLShutdownStatementAssert; @@ -152,6 +154,7 @@ import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.show.event.MySQLShowEventsStatementTestCase; import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.show.function.MySQLShowFunctionStatusStatementTestCase; import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.show.index.MySQLShowIndexStatementTestCase; +import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.show.MySQLShowTransactionStatementTestCase; import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.show.privilege.MySQLShowCreateUserStatementTestCase; import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.show.procedure.MySQLShowProcedureCodeStatementTestCase; import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.show.procedure.MySQLShowProcedureStatusStatementTestCase; @@ -277,6 +280,8 @@ public static void assertIs(final SQLCaseAssertContext assertContext, final DALS MySQLDelimiterStatementAssert.assertIs(assertContext, (MySQLDelimiterStatement) actual, (MySQLDelimiterStatementTestCase) expected); } else if (actual instanceof MySQLShowBinlogEventsStatement) { MySQLShowBinlogEventsStatementAssert.assertIs(assertContext, (MySQLShowBinlogEventsStatement) actual, (MySQLShowBinlogEventsStatementTestCase) expected); + } else if (actual instanceof MySQLShowTransactionStatement) { + MySQLShowTransactionStatementAssert.assertIs(assertContext, (MySQLShowTransactionStatement) actual, (MySQLShowTransactionStatementTestCase) expected); } } } diff --git a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/statement/dal/dialect/mysql/type/MySQLShowTransactionStatementAssert.java b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/statement/dal/dialect/mysql/type/MySQLShowTransactionStatementAssert.java new file mode 100644 index 0000000000000..ca3ae7e7202c9 --- /dev/null +++ b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/statement/dal/dialect/mysql/type/MySQLShowTransactionStatementAssert.java @@ -0,0 +1,51 @@ +/* + * 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.test.it.sql.parser.internal.asserts.statement.dal.dialect.mysql.type; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.MySQLShowTransactionStatement; +import org.apache.shardingsphere.test.it.sql.parser.internal.asserts.SQLCaseAssertContext; +import org.apache.shardingsphere.test.it.sql.parser.internal.asserts.segment.SQLSegmentAssert; +import org.apache.shardingsphere.test.it.sql.parser.internal.asserts.segment.database.DatabaseAssert; +import org.apache.shardingsphere.test.it.sql.parser.internal.asserts.segment.where.WhereClauseAssert; +import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.statement.dal.dialect.mysql.show.MySQLShowTransactionStatementTestCase; + +/** + * Show transaction statement assert for MySQL. + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class MySQLShowTransactionStatementAssert { + + /** + * Assert show transaction statement is correct with expected parser result. + * + * @param assertContext assert context + * @param actual actual show transaction statement + * @param expected expected show transaction statement test case + */ + public static void assertIs(final SQLCaseAssertContext assertContext, final MySQLShowTransactionStatement actual, final MySQLShowTransactionStatementTestCase expected) { + if (null != actual.getFromDatabase()) { + DatabaseAssert.assertIs(assertContext, actual.getFromDatabase().getDatabase(), expected.getFromDatabase().getDatabase()); + SQLSegmentAssert.assertIs(assertContext, actual.getFromDatabase(), expected.getFromDatabase()); + } + if (null != actual.getWhere()) { + WhereClauseAssert.assertIs(assertContext, actual.getWhere(), expected.getWhere()); + } + } +} diff --git a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/statement/dal/dialect/mysql/show/MySQLShowTransactionStatementTestCase.java b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/statement/dal/dialect/mysql/show/MySQLShowTransactionStatementTestCase.java index 78a56b08b897e..58d6b3872a37a 100644 --- a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/statement/dal/dialect/mysql/show/MySQLShowTransactionStatementTestCase.java +++ b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/statement/dal/dialect/mysql/show/MySQLShowTransactionStatementTestCase.java @@ -20,7 +20,7 @@ import lombok.Getter; import lombok.Setter; import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.SQLParserTestCase; -import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.segment.impl.database.ExpectedDatabase; +import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.segment.impl.from.ExpectedFromDatabase; import org.apache.shardingsphere.test.it.sql.parser.internal.cases.parser.jaxb.segment.impl.where.ExpectedWhereClause; import javax.xml.bind.annotation.XmlElement; @@ -32,8 +32,8 @@ @Setter public final class MySQLShowTransactionStatementTestCase extends SQLParserTestCase { - @XmlElement - private ExpectedDatabase database; + @XmlElement(name = "from") + private ExpectedFromDatabase fromDatabase; @XmlElement(name = "where") private ExpectedWhereClause where; diff --git a/test/it/parser/src/main/resources/case/dal/show.xml b/test/it/parser/src/main/resources/case/dal/show.xml index 7973418b4cd8e..92c4ccd7e9524 100644 --- a/test/it/parser/src/main/resources/case/dal/show.xml +++ b/test/it/parser/src/main/resources/case/dal/show.xml @@ -1100,15 +1100,15 @@ - + - + - + = - + @@ -1116,15 +1116,15 @@ - + - + - + = - + @@ -1132,16 +1132,18 @@ - - + + + + - + = - + @@ -1149,16 +1151,18 @@ - - + + + + - + = - + From c247e72e345b369652f6d95acf134e42553e30d2 Mon Sep 17 00:00:00 2001 From: xiaosaxiaozai Date: Sat, 20 Dec 2025 11:08:01 +0800 Subject: [PATCH 3/6] Support for the SHOW TRANSACTION statement and the L2_DISTANCE function --- .../main/antlr4/imports/doris/DALStatement.g4 | 2 +- .../show/MySQLShowTransactionExecutor.java | 91 ++++++++ .../MySQLShowAdminExecutorFactory.java | 5 + .../admin/MySQLAdminExecutorCreatorTest.java | 10 + .../MySQLShowTransactionExecutorTest.java | 197 ++++++++++++++++++ .../src/main/resources/case/dal/show.xml | 8 + .../main/resources/sql/supported/dal/show.xml | 2 + 7 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java create mode 100644 proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java diff --git a/parser/sql/engine/dialect/doris/src/main/antlr4/imports/doris/DALStatement.g4 b/parser/sql/engine/dialect/doris/src/main/antlr4/imports/doris/DALStatement.g4 index 73cd6bb5451dc..4b884e6a54f7a 100644 --- a/parser/sql/engine/dialect/doris/src/main/antlr4/imports/doris/DALStatement.g4 +++ b/parser/sql/engine/dialect/doris/src/main/antlr4/imports/doris/DALStatement.g4 @@ -255,7 +255,7 @@ showCharset ; showTransaction - : SHOW TRANSACTION fromDatabase? showWhereClause + : SHOW TRANSACTION fromDatabase? showWhereClause? ; setCharacter diff --git a/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java b/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java new file mode 100644 index 0000000000000..6e756f3256943 --- /dev/null +++ b/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shardingsphere.proxy.backend.mysql.handler.admin.executor.show; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.shardingsphere.database.exception.core.exception.syntax.database.UnknownDatabaseException; +import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions; +import org.apache.shardingsphere.infra.executor.sql.execute.result.query.QueryResultMetaData; +import org.apache.shardingsphere.infra.executor.sql.execute.result.query.impl.raw.metadata.RawQueryResultColumnMetaData; +import org.apache.shardingsphere.infra.executor.sql.execute.result.query.impl.raw.metadata.RawQueryResultMetaData; +import org.apache.shardingsphere.infra.merge.result.MergedResult; +import org.apache.shardingsphere.infra.merge.result.impl.local.LocalDataMergedResult; +import org.apache.shardingsphere.infra.merge.result.impl.local.LocalDataQueryResultRow; +import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData; +import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase; +import org.apache.shardingsphere.proxy.backend.handler.admin.executor.DatabaseAdminQueryExecutor; +import org.apache.shardingsphere.proxy.backend.session.ConnectionSession; +import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.MySQLShowTransactionStatement; + +import java.sql.Types; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * Show transaction executor for MySQL. + */ +@RequiredArgsConstructor +public final class MySQLShowTransactionExecutor implements DatabaseAdminQueryExecutor { + + private final MySQLShowTransactionStatement sqlStatement; + + @Getter + private QueryResultMetaData queryResultMetaData; + + @Getter + private MergedResult mergedResult; + + @Override + public void execute(final ConnectionSession connectionSession, final ShardingSphereMetaData metaData) { + String databaseName = getDatabaseName(connectionSession); + if (null != databaseName) { + ShardingSpherePreconditions.checkState(metaData.containsDatabase(databaseName), () -> new UnknownDatabaseException(databaseName)); + } + queryResultMetaData = createQueryResultMetaData(); + mergedResult = new LocalDataMergedResult(getQueryResultRows(databaseName, metaData)); + } + + private String getDatabaseName(final ConnectionSession connectionSession) { + return null == sqlStatement.getFromDatabase() ? connectionSession.getUsedDatabaseName() : sqlStatement.getFromDatabase().getDatabase().getIdentifier().getValue(); + } + + private QueryResultMetaData createQueryResultMetaData() { + List columnNames = new ArrayList<>(6); + columnNames.add(new RawQueryResultColumnMetaData("", "TransactionId", "TransactionId", Types.BIGINT, "BIGINT", 20, 0)); + columnNames.add(new RawQueryResultColumnMetaData("", "Label", "Label", Types.VARCHAR, "VARCHAR", 255, 0)); + columnNames.add(new RawQueryResultColumnMetaData("", "Database", "Database", Types.VARCHAR, "VARCHAR", 255, 0)); + columnNames.add(new RawQueryResultColumnMetaData("", "TransactionStatus", "TransactionStatus", Types.VARCHAR, "VARCHAR", 50, 0)); + columnNames.add(new RawQueryResultColumnMetaData("", "LoadStartTime", "LoadStartTime", Types.VARCHAR, "VARCHAR", 50, 0)); + columnNames.add(new RawQueryResultColumnMetaData("", "LoadFinishTime", "LoadFinishTime", Types.VARCHAR, "VARCHAR", 50, 0)); + return new RawQueryResultMetaData(columnNames); + } + + private Collection getQueryResultRows(final String databaseName, final ShardingSphereMetaData metaData) { + if (null == databaseName) { + return Collections.emptyList(); + } + ShardingSphereDatabase database = metaData.getDatabase(databaseName); + if (!database.isComplete()) { + return Collections.emptyList(); + } + return Collections.emptyList(); + } +} diff --git a/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/factory/MySQLShowAdminExecutorFactory.java b/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/factory/MySQLShowAdminExecutorFactory.java index 6865ae5b3a6f1..9d7afb323be06 100644 --- a/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/factory/MySQLShowAdminExecutorFactory.java +++ b/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/factory/MySQLShowAdminExecutorFactory.java @@ -26,12 +26,14 @@ import org.apache.shardingsphere.proxy.backend.mysql.handler.admin.executor.show.MySQLShowProcedureStatusExecutor; import org.apache.shardingsphere.proxy.backend.mysql.handler.admin.executor.show.MySQLShowProcessListExecutor; import org.apache.shardingsphere.proxy.backend.mysql.handler.admin.executor.show.MySQLShowTablesExecutor; +import org.apache.shardingsphere.proxy.backend.mysql.handler.admin.executor.show.MySQLShowTransactionExecutor; import org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.database.MySQLShowCreateDatabaseStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.database.MySQLShowDatabasesStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.function.MySQLShowFunctionStatusStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.procedure.MySQLShowProcedureStatusStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.process.MySQLShowProcessListStatement; +import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.MySQLShowTransactionStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.table.MySQLShowTablesStatement; import java.util.Optional; @@ -67,6 +69,9 @@ public static Optional newInstance(final SQLStatement sql if (sqlStatement instanceof MySQLShowProcessListStatement) { return Optional.of(new MySQLShowProcessListExecutor((MySQLShowProcessListStatement) sqlStatement)); } + if (sqlStatement instanceof MySQLShowTransactionStatement) { + return Optional.of(new MySQLShowTransactionExecutor((MySQLShowTransactionStatement) sqlStatement)); + } return Optional.empty(); } } diff --git a/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/MySQLAdminExecutorCreatorTest.java b/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/MySQLAdminExecutorCreatorTest.java index 7f69967391c98..ec72fb3fe4dac 100644 --- a/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/MySQLAdminExecutorCreatorTest.java +++ b/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/MySQLAdminExecutorCreatorTest.java @@ -42,6 +42,7 @@ import org.apache.shardingsphere.proxy.backend.mysql.handler.admin.executor.show.MySQLShowProcedureStatusExecutor; import org.apache.shardingsphere.proxy.backend.mysql.handler.admin.executor.show.MySQLShowProcessListExecutor; import org.apache.shardingsphere.proxy.backend.mysql.handler.admin.executor.show.MySQLShowTablesExecutor; +import org.apache.shardingsphere.proxy.backend.mysql.handler.admin.executor.show.MySQLShowTransactionExecutor; import org.apache.shardingsphere.proxy.backend.mysql.handler.admin.executor.MySQLUseDatabaseExecutor; import org.apache.shardingsphere.proxy.backend.mysql.handler.admin.executor.select.NoResourceShowExecutor; import org.apache.shardingsphere.proxy.backend.mysql.handler.admin.executor.select.SelectInformationSchemataExecutor; @@ -66,6 +67,7 @@ import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.function.MySQLShowFunctionStatusStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.procedure.MySQLShowProcedureStatusStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.process.MySQLShowProcessListStatement; +import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.MySQLShowTransactionStatement; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.table.MySQLShowTablesStatement; import org.apache.shardingsphere.test.infra.fixture.jdbc.MockedDataSource; import org.apache.shardingsphere.test.infra.framework.extension.mock.AutoMockExtension; @@ -118,6 +120,14 @@ void assertCreateWithShowTables() { assertThat(actual.get(), isA(MySQLShowTablesExecutor.class)); } + @Test + void assertCreateWithShowTransaction() { + SQLStatementContext sqlStatementContext = new CommonSQLStatementContext(new MySQLShowTransactionStatement(databaseType)); + Optional actual = new MySQLAdminExecutorCreator().create(sqlStatementContext, "", "", Collections.emptyList()); + assertTrue(actual.isPresent()); + assertThat(actual.get(), isA(MySQLShowTransactionExecutor.class)); + } + @Test void assertCreateWithUse() { SQLStatementContext sqlStatementContext = new CommonSQLStatementContext(new MySQLUseStatement(databaseType, null)); diff --git a/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java b/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java new file mode 100644 index 0000000000000..2eca20c826fc8 --- /dev/null +++ b/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java @@ -0,0 +1,197 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shardingsphere.proxy.backend.mysql.handler.admin.executor.show; + +import org.apache.shardingsphere.authority.rule.AuthorityRule; +import org.apache.shardingsphere.database.connector.core.type.DatabaseType; +import org.apache.shardingsphere.database.exception.core.exception.syntax.database.UnknownDatabaseException; +import org.apache.shardingsphere.infra.config.props.ConfigurationProperties; +import org.apache.shardingsphere.infra.executor.sql.execute.result.query.QueryResultMetaData; +import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData; +import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase; +import org.apache.shardingsphere.infra.metadata.database.resource.ResourceMetaData; +import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData; +import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader; +import org.apache.shardingsphere.proxy.backend.session.ConnectionSession; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dal.FromDatabaseSegment; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.BinaryOperationExpression; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.simple.LiteralExpressionSegment; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.predicate.WhereSegment; +import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.DatabaseSegment; +import org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue; +import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.MySQLShowTransactionStatement; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import java.sql.SQLException; +import java.sql.Types; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Properties; + +import static org.hamcrest.CoreMatchers.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.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class MySQLShowTransactionExecutorTest { + + private static final String DATABASE_NAME = "test_db"; + + private final DatabaseType databaseType = TypedSPILoader.getService(DatabaseType.class, "MySQL"); + + @Test + void assertExecuteWithoutFromAndWhere() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); + executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); + assertQueryResultMetaData(executor.getQueryResultMetaData()); + assertFalse(executor.getMergedResult().next()); + } + + @Test + void assertExecuteWithFromDatabase() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + FromDatabaseSegment fromDatabaseSegment = new FromDatabaseSegment(0, new DatabaseSegment(0, 0, new IdentifierValue(DATABASE_NAME))); + sqlStatement.setFromDatabase(fromDatabaseSegment); + MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); + executor.execute(mockConnectionSession("other_db"), mockMetaData(createDatabases())); + assertQueryResultMetaData(executor.getQueryResultMetaData()); + assertFalse(executor.getMergedResult().next()); + } + + @Test + void assertExecuteWithWhereId() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("ID", 4005); + sqlStatement.setWhere(whereSegment); + MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); + executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); + assertQueryResultMetaData(executor.getQueryResultMetaData()); + assertFalse(executor.getMergedResult().next()); + } + + @Test + void assertExecuteWithWhereLabel() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("label", "test_label"); + sqlStatement.setWhere(whereSegment); + MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); + executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); + assertQueryResultMetaData(executor.getQueryResultMetaData()); + assertFalse(executor.getMergedResult().next()); + } + + @Test + void assertExecuteWithFromAndWhereId() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + FromDatabaseSegment fromDatabaseSegment = new FromDatabaseSegment(0, new DatabaseSegment(0, 0, new IdentifierValue(DATABASE_NAME))); + sqlStatement.setFromDatabase(fromDatabaseSegment); + WhereSegment whereSegment = createWhereSegment("ID", 4005); + sqlStatement.setWhere(whereSegment); + MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); + executor.execute(mockConnectionSession("other_db"), mockMetaData(createDatabases())); + assertQueryResultMetaData(executor.getQueryResultMetaData()); + assertFalse(executor.getMergedResult().next()); + } + + @Test + void assertExecuteWithFromAndWhereLabel() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + FromDatabaseSegment fromDatabaseSegment = new FromDatabaseSegment(0, new DatabaseSegment(0, 0, new IdentifierValue(DATABASE_NAME))); + sqlStatement.setFromDatabase(fromDatabaseSegment); + WhereSegment whereSegment = createWhereSegment("label", "test_label"); + sqlStatement.setWhere(whereSegment); + MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); + executor.execute(mockConnectionSession("other_db"), mockMetaData(createDatabases())); + assertQueryResultMetaData(executor.getQueryResultMetaData()); + assertFalse(executor.getMergedResult().next()); + } + + @Test + void assertExecuteWithUnknownDatabase() { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + FromDatabaseSegment fromDatabaseSegment = new FromDatabaseSegment(0, new DatabaseSegment(0, 0, new IdentifierValue("unknown_db"))); + sqlStatement.setFromDatabase(fromDatabaseSegment); + MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); + assertThrows(UnknownDatabaseException.class, () -> executor.execute(mockConnectionSession("other_db"), mockMetaData(createDatabases()))); + } + + @Test + void assertExecuteWithUncompletedDatabase() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + FromDatabaseSegment fromDatabaseSegment = new FromDatabaseSegment(0, new DatabaseSegment(0, 0, new IdentifierValue("uncompleted"))); + sqlStatement.setFromDatabase(fromDatabaseSegment); + MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); + executor.execute(mockConnectionSession("uncompleted"), mockMetaData(createDatabases())); + assertQueryResultMetaData(executor.getQueryResultMetaData()); + assertFalse(executor.getMergedResult().next()); + } + + private void assertQueryResultMetaData(final QueryResultMetaData metaData) throws SQLException { + assertThat(metaData.getColumnCount(), is(6)); + assertThat(metaData.getColumnLabel(1), is("TransactionId")); + assertThat(metaData.getColumnType(1), is(Types.BIGINT)); + assertThat(metaData.getColumnLabel(2), is("Label")); + assertThat(metaData.getColumnType(2), is(Types.VARCHAR)); + assertThat(metaData.getColumnLabel(3), is("Database")); + assertThat(metaData.getColumnType(3), is(Types.VARCHAR)); + assertThat(metaData.getColumnLabel(4), is("TransactionStatus")); + assertThat(metaData.getColumnType(4), is(Types.VARCHAR)); + assertThat(metaData.getColumnLabel(5), is("LoadStartTime")); + assertThat(metaData.getColumnType(5), is(Types.VARCHAR)); + assertThat(metaData.getColumnLabel(6), is("LoadFinishTime")); + assertThat(metaData.getColumnType(6), is(Types.VARCHAR)); + } + + private WhereSegment createWhereSegment(final String columnName, final Object value) { + ColumnSegment columnSegment = new ColumnSegment(0, 0, new IdentifierValue(columnName)); + LiteralExpressionSegment literalSegment = new LiteralExpressionSegment(0, 0, value); + BinaryOperationExpression expression = new BinaryOperationExpression(0, 0, columnSegment, literalSegment, "=", ""); + return new WhereSegment(0, 0, expression); + } + + private ShardingSphereMetaData mockMetaData(final Collection databases) { + return new ShardingSphereMetaData(databases, mock(ResourceMetaData.class), new RuleMetaData(Collections.singleton(mock(AuthorityRule.class))), new ConfigurationProperties(new Properties())); + } + + private Collection createDatabases() { + ShardingSphereDatabase database = mock(ShardingSphereDatabase.class, RETURNS_DEEP_STUBS); + when(database.getName()).thenReturn(DATABASE_NAME); + when(database.getProtocolType()).thenReturn(databaseType); + when(database.isComplete()).thenReturn(true); + ShardingSphereDatabase uncompletedDatabase = new ShardingSphereDatabase("uncompleted", mock(), mock(), mock(), Collections.emptyList()); + return Arrays.asList(database, uncompletedDatabase); + } + + private ConnectionSession mockConnectionSession(final String usedDatabaseName) { + ConnectionSession result = mock(ConnectionSession.class, RETURNS_DEEP_STUBS); + when(result.getUsedDatabaseName()).thenReturn(usedDatabaseName); + return result; + } +} diff --git a/test/it/parser/src/main/resources/case/dal/show.xml b/test/it/parser/src/main/resources/case/dal/show.xml index 92c4ccd7e9524..72cb17c1ab193 100644 --- a/test/it/parser/src/main/resources/case/dal/show.xml +++ b/test/it/parser/src/main/resources/case/dal/show.xml @@ -1099,6 +1099,14 @@ + + + + + + + + diff --git a/test/it/parser/src/main/resources/sql/supported/dal/show.xml b/test/it/parser/src/main/resources/sql/supported/dal/show.xml index e4f5f91f71f8c..0c86e33f4d2da 100644 --- a/test/it/parser/src/main/resources/sql/supported/dal/show.xml +++ b/test/it/parser/src/main/resources/sql/supported/dal/show.xml @@ -235,6 +235,8 @@ + + From 67e1b237ad3e4809cea2b456f8a20ab549a8d5e2 Mon Sep 17 00:00:00 2001 From: xiaosaxiaozai Date: Sun, 21 Dec 2025 11:38:16 +0800 Subject: [PATCH 4/6] Support for the SHOW TRANSACTION statement and the L2_DISTANCE function --- .../show/MySQLShowTransactionExecutor.java | 121 ++++++++++++++++-- .../MySQLShowTransactionExecutorTest.java | 48 ++++++- 2 files changed, 157 insertions(+), 12 deletions(-) diff --git a/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java b/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java index 6e756f3256943..942c348c9049f 100644 --- a/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java +++ b/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java @@ -31,6 +31,9 @@ import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase; import org.apache.shardingsphere.proxy.backend.handler.admin.executor.DatabaseAdminQueryExecutor; import org.apache.shardingsphere.proxy.backend.session.ConnectionSession; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.BinaryOperationExpression; +import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.simple.LiteralExpressionSegment; import org.apache.shardingsphere.sql.parser.statement.mysql.dal.show.MySQLShowTransactionStatement; import java.sql.Types; @@ -38,6 +41,8 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; /** * Show transaction executor for MySQL. @@ -53,6 +58,10 @@ public final class MySQLShowTransactionExecutor implements DatabaseAdminQueryExe @Getter private MergedResult mergedResult; + private Optional filterTransactionId = Optional.empty(); + + private Optional filterLabel = Optional.empty(); + @Override public void execute(final ConnectionSession connectionSession, final ShardingSphereMetaData metaData) { String databaseName = getDatabaseName(connectionSession); @@ -68,14 +77,20 @@ private String getDatabaseName(final ConnectionSession connectionSession) { } private QueryResultMetaData createQueryResultMetaData() { - List columnNames = new ArrayList<>(6); - columnNames.add(new RawQueryResultColumnMetaData("", "TransactionId", "TransactionId", Types.BIGINT, "BIGINT", 20, 0)); - columnNames.add(new RawQueryResultColumnMetaData("", "Label", "Label", Types.VARCHAR, "VARCHAR", 255, 0)); - columnNames.add(new RawQueryResultColumnMetaData("", "Database", "Database", Types.VARCHAR, "VARCHAR", 255, 0)); - columnNames.add(new RawQueryResultColumnMetaData("", "TransactionStatus", "TransactionStatus", Types.VARCHAR, "VARCHAR", 50, 0)); - columnNames.add(new RawQueryResultColumnMetaData("", "LoadStartTime", "LoadStartTime", Types.VARCHAR, "VARCHAR", 50, 0)); - columnNames.add(new RawQueryResultColumnMetaData("", "LoadFinishTime", "LoadFinishTime", Types.VARCHAR, "VARCHAR", 50, 0)); - return new RawQueryResultMetaData(columnNames); + List columns = new ArrayList<>(15); + columns.add(new RawQueryResultColumnMetaData("", "TransactionId", "TransactionId", Types.BIGINT, "BIGINT", 20, 0)); + columns.add(new RawQueryResultColumnMetaData("", "Label", "Label", Types.VARCHAR, "VARCHAR", 255, 0)); + columns.add(new RawQueryResultColumnMetaData("", "Coordinator", "Coordinator", Types.VARCHAR, "VARCHAR", 255, 0)); + columns.add(new RawQueryResultColumnMetaData("", "TransactionStatus", "TransactionStatus", Types.VARCHAR, "VARCHAR", 50, 0)); + columns.add(new RawQueryResultColumnMetaData("", "LoadJobSourceType", "LoadJobSourceType", Types.VARCHAR, "VARCHAR", 50, 0)); + columns.add(new RawQueryResultColumnMetaData("", "PrepareTime", "PrepareTime", Types.VARCHAR, "VARCHAR", 50, 0)); + columns.add(new RawQueryResultColumnMetaData("", "CommitTime", "CommitTime", Types.VARCHAR, "VARCHAR", 50, 0)); + columns.add(new RawQueryResultColumnMetaData("", "FinishTime", "FinishTime", Types.VARCHAR, "VARCHAR", 50, 0)); + columns.add(new RawQueryResultColumnMetaData("", "Reason", "Reason", Types.VARCHAR, "VARCHAR", 1000, 0)); + columns.add(new RawQueryResultColumnMetaData("", "ErrorReplicasCount", "ErrorReplicasCount", Types.INTEGER, "INTEGER", 10, 0)); + columns.add(new RawQueryResultColumnMetaData("", "ListenerId", "ListenerId", Types.BIGINT, "BIGINT", 20, 0)); + columns.add(new RawQueryResultColumnMetaData("", "TimeoutMs", "TimeoutMs", Types.BIGINT, "BIGINT", 20, 0)); + return new RawQueryResultMetaData(columns); } private Collection getQueryResultRows(final String databaseName, final ShardingSphereMetaData metaData) { @@ -86,6 +101,96 @@ private Collection getQueryResultRows(final String data if (!database.isComplete()) { return Collections.emptyList(); } + extractWhereFilter(); + Collection transactions = loadTransactions(); + return transactions.stream() + .filter(this::matchesFilter) + .map(this::buildTransactionRow) + .collect(Collectors.toList()); + } + + private void extractWhereFilter() { + if (null == sqlStatement.getWhere()) { + return; + } + if (sqlStatement.getWhere().getExpr() instanceof BinaryOperationExpression) { + BinaryOperationExpression binaryExpr = (BinaryOperationExpression) sqlStatement.getWhere().getExpr(); + extractFilterFromBinaryExpression(binaryExpr); + } + } + + private void extractFilterFromBinaryExpression(final BinaryOperationExpression expression) { + if (expression.getLeft() instanceof ColumnSegment && expression.getRight() instanceof LiteralExpressionSegment) { + ColumnSegment column = (ColumnSegment) expression.getLeft(); + LiteralExpressionSegment literal = (LiteralExpressionSegment) expression.getRight(); + String columnName = column.getIdentifier().getValue().toLowerCase(); + if ("id".equals(columnName)) { + filterTransactionId = Optional.of(((Number) literal.getLiterals()).longValue()); + } else if ("label".equals(columnName)) { + filterLabel = Optional.of(String.valueOf(literal.getLiterals())); + } + } + } + + private boolean matchesFilter(final TransactionInfo transaction) { + if (filterTransactionId.isPresent() && filterTransactionId.get() != transaction.getTransactionId()) { + return false; + } + if (filterLabel.isPresent() && !filterLabel.get().equals(transaction.getLabel())) { + return false; + } + return true; + } + + private LocalDataQueryResultRow buildTransactionRow(final TransactionInfo transaction) { + return new LocalDataQueryResultRow( + transaction.getTransactionId(), + transaction.getLabel(), + transaction.getCoordinator(), + transaction.getTransactionStatus(), + transaction.getLoadJobSourceType(), + transaction.getPrepareTime(), + transaction.getCommitTime(), + transaction.getFinishTime(), + transaction.getReason(), + transaction.getErrorReplicasCount(), + transaction.getListenerId(), + transaction.getTimeoutMs()); + } + + private Collection loadTransactions() { return Collections.emptyList(); } + + /** + * Transaction information holder. + */ + @Getter + @RequiredArgsConstructor + static class TransactionInfo { + + private final long transactionId; + + private final String label; + + private final String coordinator; + + private final String transactionStatus; + + private final String loadJobSourceType; + + private final String prepareTime; + + private final String commitTime; + + private final String finishTime; + + private final String reason; + + private final int errorReplicasCount; + + private final long listenerId; + + private final long timeoutMs; + } } diff --git a/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java b/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java index 2eca20c826fc8..215aa9035bb01 100644 --- a/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java +++ b/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java @@ -153,20 +153,60 @@ void assertExecuteWithUncompletedDatabase() throws SQLException { assertFalse(executor.getMergedResult().next()); } + @Test + void assertTransactionStatusConstants() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); + executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); + assertQueryResultMetaData(executor.getQueryResultMetaData()); + } + + @Test + void assertBuildTransactionRowWithAllColumns() { + MySQLShowTransactionExecutor.TransactionInfo transactionInfo = new MySQLShowTransactionExecutor.TransactionInfo( + 4005L, "test_label", "coordinator_node", "VISIBLE", "ROUTINE_LOAD", + "2025-01-01 10:00:00", "2025-01-01 10:01:00", "2025-01-01 10:02:00", + "", 0, 12345L, 60000L); + assertThat(transactionInfo.getTransactionId(), is(4005L)); + assertThat(transactionInfo.getLabel(), is("test_label")); + assertThat(transactionInfo.getCoordinator(), is("coordinator_node")); + assertThat(transactionInfo.getTransactionStatus(), is("VISIBLE")); + assertThat(transactionInfo.getLoadJobSourceType(), is("ROUTINE_LOAD")); + assertThat(transactionInfo.getPrepareTime(), is("2025-01-01 10:00:00")); + assertThat(transactionInfo.getCommitTime(), is("2025-01-01 10:01:00")); + assertThat(transactionInfo.getFinishTime(), is("2025-01-01 10:02:00")); + assertThat(transactionInfo.getReason(), is("")); + assertThat(transactionInfo.getErrorReplicasCount(), is(0)); + assertThat(transactionInfo.getListenerId(), is(12345L)); + assertThat(transactionInfo.getTimeoutMs(), is(60000L)); + } + private void assertQueryResultMetaData(final QueryResultMetaData metaData) throws SQLException { - assertThat(metaData.getColumnCount(), is(6)); + assertThat(metaData.getColumnCount(), is(12)); assertThat(metaData.getColumnLabel(1), is("TransactionId")); assertThat(metaData.getColumnType(1), is(Types.BIGINT)); assertThat(metaData.getColumnLabel(2), is("Label")); assertThat(metaData.getColumnType(2), is(Types.VARCHAR)); - assertThat(metaData.getColumnLabel(3), is("Database")); + assertThat(metaData.getColumnLabel(3), is("Coordinator")); assertThat(metaData.getColumnType(3), is(Types.VARCHAR)); assertThat(metaData.getColumnLabel(4), is("TransactionStatus")); assertThat(metaData.getColumnType(4), is(Types.VARCHAR)); - assertThat(metaData.getColumnLabel(5), is("LoadStartTime")); + assertThat(metaData.getColumnLabel(5), is("LoadJobSourceType")); assertThat(metaData.getColumnType(5), is(Types.VARCHAR)); - assertThat(metaData.getColumnLabel(6), is("LoadFinishTime")); + assertThat(metaData.getColumnLabel(6), is("PrepareTime")); assertThat(metaData.getColumnType(6), is(Types.VARCHAR)); + assertThat(metaData.getColumnLabel(7), is("CommitTime")); + assertThat(metaData.getColumnType(7), is(Types.VARCHAR)); + assertThat(metaData.getColumnLabel(8), is("FinishTime")); + assertThat(metaData.getColumnType(8), is(Types.VARCHAR)); + assertThat(metaData.getColumnLabel(9), is("Reason")); + assertThat(metaData.getColumnType(9), is(Types.VARCHAR)); + assertThat(metaData.getColumnLabel(10), is("ErrorReplicasCount")); + assertThat(metaData.getColumnType(10), is(Types.INTEGER)); + assertThat(metaData.getColumnLabel(11), is("ListenerId")); + assertThat(metaData.getColumnType(11), is(Types.BIGINT)); + assertThat(metaData.getColumnLabel(12), is("TimeoutMs")); + assertThat(metaData.getColumnType(12), is(Types.BIGINT)); } private WhereSegment createWhereSegment(final String columnName, final Object value) { From 5f4c7cf156f3bc09b59116b52e1d512d3bf92013 Mon Sep 17 00:00:00 2001 From: xiaosaxiaozai Date: Sun, 21 Dec 2025 16:59:59 +0800 Subject: [PATCH 5/6] Support for the SHOW TRANSACTION statement and the L2_DISTANCE function --- .../show/MySQLShowTransactionExecutor.java | 38 ++- .../MySQLShowTransactionExecutorTest.java | 285 +++++++++++++++--- 2 files changed, 262 insertions(+), 61 deletions(-) diff --git a/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java b/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java index 942c348c9049f..eefd39654a733 100644 --- a/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java +++ b/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java @@ -48,7 +48,7 @@ * Show transaction executor for MySQL. */ @RequiredArgsConstructor -public final class MySQLShowTransactionExecutor implements DatabaseAdminQueryExecutor { +public class MySQLShowTransactionExecutor implements DatabaseAdminQueryExecutor { private final MySQLShowTransactionStatement sqlStatement; @@ -120,16 +120,32 @@ private void extractWhereFilter() { } private void extractFilterFromBinaryExpression(final BinaryOperationExpression expression) { - if (expression.getLeft() instanceof ColumnSegment && expression.getRight() instanceof LiteralExpressionSegment) { - ColumnSegment column = (ColumnSegment) expression.getLeft(); - LiteralExpressionSegment literal = (LiteralExpressionSegment) expression.getRight(); - String columnName = column.getIdentifier().getValue().toLowerCase(); - if ("id".equals(columnName)) { - filterTransactionId = Optional.of(((Number) literal.getLiterals()).longValue()); - } else if ("label".equals(columnName)) { - filterLabel = Optional.of(String.valueOf(literal.getLiterals())); + if (!(expression.getLeft() instanceof ColumnSegment) || !(expression.getRight() instanceof LiteralExpressionSegment)) { + return; + } + ColumnSegment column = (ColumnSegment) expression.getLeft(); + LiteralExpressionSegment literal = (LiteralExpressionSegment) expression.getRight(); + String columnName = column.getIdentifier().getValue().toLowerCase(); + Object literalValue = literal.getLiterals(); + if ("id".equalsIgnoreCase(columnName)) { + filterTransactionId = extractLongValue(literalValue); + } else if ("label".equalsIgnoreCase(columnName)) { + filterLabel = Optional.of(String.valueOf(literalValue)); + } + } + + private Optional extractLongValue(final Object value) { + if (value instanceof Number) { + return Optional.of(((Number) value).longValue()); + } + if (value instanceof String) { + try { + return Optional.of(Long.parseLong((String) value)); + } catch (final NumberFormatException ignored) { + return Optional.empty(); } } + return Optional.empty(); } private boolean matchesFilter(final TransactionInfo transaction) { @@ -158,8 +174,8 @@ private LocalDataQueryResultRow buildTransactionRow(final TransactionInfo transa transaction.getTimeoutMs()); } - private Collection loadTransactions() { - return Collections.emptyList(); + protected Collection loadTransactions() { + throw new UnsupportedOperationException("SHOW TRANSACTION is not supported for the moment. "); } /** diff --git a/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java b/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java index 215aa9035bb01..7a74ce54a58bf 100644 --- a/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java +++ b/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java @@ -22,6 +22,7 @@ import org.apache.shardingsphere.database.exception.core.exception.syntax.database.UnknownDatabaseException; import org.apache.shardingsphere.infra.config.props.ConfigurationProperties; import org.apache.shardingsphere.infra.executor.sql.execute.result.query.QueryResultMetaData; +import org.apache.shardingsphere.infra.merge.result.MergedResult; import org.apache.shardingsphere.infra.metadata.ShardingSphereMetaData; import org.apache.shardingsphere.infra.metadata.database.ShardingSphereDatabase; import org.apache.shardingsphere.infra.metadata.database.resource.ResourceMetaData; @@ -44,15 +45,18 @@ import java.sql.SQLException; import java.sql.Types; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Properties; import static org.hamcrest.CoreMatchers.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; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -66,71 +70,66 @@ class MySQLShowTransactionExecutorTest { private final DatabaseType databaseType = TypedSPILoader.getService(DatabaseType.class, "MySQL"); @Test - void assertExecuteWithoutFromAndWhere() throws SQLException { + void assertExecuteWithoutFromAndWhere() { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); - executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); - assertQueryResultMetaData(executor.getQueryResultMetaData()); - assertFalse(executor.getMergedResult().next()); + UnsupportedOperationException exception = assertThrows(UnsupportedOperationException.class, + () -> executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases()))); + assertThat(exception.getMessage(), is("SHOW TRANSACTION is not supported for the moment. ")); } @Test - void assertExecuteWithFromDatabase() throws SQLException { + void assertExecuteWithFromDatabase() { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); FromDatabaseSegment fromDatabaseSegment = new FromDatabaseSegment(0, new DatabaseSegment(0, 0, new IdentifierValue(DATABASE_NAME))); sqlStatement.setFromDatabase(fromDatabaseSegment); MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); - executor.execute(mockConnectionSession("other_db"), mockMetaData(createDatabases())); - assertQueryResultMetaData(executor.getQueryResultMetaData()); - assertFalse(executor.getMergedResult().next()); + assertThrows(UnsupportedOperationException.class, + () -> executor.execute(mockConnectionSession("other_db"), mockMetaData(createDatabases()))); } @Test - void assertExecuteWithWhereId() throws SQLException { + void assertExecuteWithWhereId() { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); WhereSegment whereSegment = createWhereSegment("ID", 4005); sqlStatement.setWhere(whereSegment); MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); - executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); - assertQueryResultMetaData(executor.getQueryResultMetaData()); - assertFalse(executor.getMergedResult().next()); + assertThrows(UnsupportedOperationException.class, + () -> executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases()))); } @Test - void assertExecuteWithWhereLabel() throws SQLException { + void assertExecuteWithWhereLabel() { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); WhereSegment whereSegment = createWhereSegment("label", "test_label"); sqlStatement.setWhere(whereSegment); MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); - executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); - assertQueryResultMetaData(executor.getQueryResultMetaData()); - assertFalse(executor.getMergedResult().next()); + assertThrows(UnsupportedOperationException.class, + () -> executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases()))); } @Test - void assertExecuteWithFromAndWhereId() throws SQLException { + void assertExecuteWithFromAndWhereId() { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); FromDatabaseSegment fromDatabaseSegment = new FromDatabaseSegment(0, new DatabaseSegment(0, 0, new IdentifierValue(DATABASE_NAME))); sqlStatement.setFromDatabase(fromDatabaseSegment); WhereSegment whereSegment = createWhereSegment("ID", 4005); sqlStatement.setWhere(whereSegment); MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); - executor.execute(mockConnectionSession("other_db"), mockMetaData(createDatabases())); - assertQueryResultMetaData(executor.getQueryResultMetaData()); - assertFalse(executor.getMergedResult().next()); + assertThrows(UnsupportedOperationException.class, + () -> executor.execute(mockConnectionSession("other_db"), mockMetaData(createDatabases()))); } @Test - void assertExecuteWithFromAndWhereLabel() throws SQLException { + void assertExecuteWithFromAndWhereLabel() { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); FromDatabaseSegment fromDatabaseSegment = new FromDatabaseSegment(0, new DatabaseSegment(0, 0, new IdentifierValue(DATABASE_NAME))); sqlStatement.setFromDatabase(fromDatabaseSegment); WhereSegment whereSegment = createWhereSegment("label", "test_label"); sqlStatement.setWhere(whereSegment); MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); - executor.execute(mockConnectionSession("other_db"), mockMetaData(createDatabases())); - assertQueryResultMetaData(executor.getQueryResultMetaData()); - assertFalse(executor.getMergedResult().next()); + assertThrows(UnsupportedOperationException.class, + () -> executor.execute(mockConnectionSession("other_db"), mockMetaData(createDatabases()))); } @Test @@ -143,45 +142,69 @@ void assertExecuteWithUnknownDatabase() { } @Test - void assertExecuteWithUncompletedDatabase() throws SQLException { + void assertExecuteWithUncompletedDatabase() { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); FromDatabaseSegment fromDatabaseSegment = new FromDatabaseSegment(0, new DatabaseSegment(0, 0, new IdentifierValue("uncompleted"))); sqlStatement.setFromDatabase(fromDatabaseSegment); MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); executor.execute(mockConnectionSession("uncompleted"), mockMetaData(createDatabases())); - assertQueryResultMetaData(executor.getQueryResultMetaData()); - assertFalse(executor.getMergedResult().next()); } @Test - void assertTransactionStatusConstants() throws SQLException { + void assertTransactionStatusConstants() { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); - executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); - assertQueryResultMetaData(executor.getQueryResultMetaData()); + assertThrows(UnsupportedOperationException.class, + () -> executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases()))); } @Test - void assertBuildTransactionRowWithAllColumns() { - MySQLShowTransactionExecutor.TransactionInfo transactionInfo = new MySQLShowTransactionExecutor.TransactionInfo( - 4005L, "test_label", "coordinator_node", "VISIBLE", "ROUTINE_LOAD", - "2025-01-01 10:00:00", "2025-01-01 10:01:00", "2025-01-01 10:02:00", - "", 0, 12345L, 60000L); - assertThat(transactionInfo.getTransactionId(), is(4005L)); - assertThat(transactionInfo.getLabel(), is("test_label")); - assertThat(transactionInfo.getCoordinator(), is("coordinator_node")); - assertThat(transactionInfo.getTransactionStatus(), is("VISIBLE")); - assertThat(transactionInfo.getLoadJobSourceType(), is("ROUTINE_LOAD")); - assertThat(transactionInfo.getPrepareTime(), is("2025-01-01 10:00:00")); - assertThat(transactionInfo.getCommitTime(), is("2025-01-01 10:01:00")); - assertThat(transactionInfo.getFinishTime(), is("2025-01-01 10:02:00")); - assertThat(transactionInfo.getReason(), is("")); - assertThat(transactionInfo.getErrorReplicasCount(), is(0)); - assertThat(transactionInfo.getListenerId(), is(12345L)); - assertThat(transactionInfo.getTimeoutMs(), is(60000L)); - } - - private void assertQueryResultMetaData(final QueryResultMetaData metaData) throws SQLException { + void assertWhereIdFilterWithNumberLiteral() { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("id", 1001L); + sqlStatement.setWhere(whereSegment); + MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); + assertThrows(UnsupportedOperationException.class, + () -> executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases()))); + } + + @Test + void assertWhereIdFilterWithStringLiteral() { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("id", "1001"); + sqlStatement.setWhere(whereSegment); + MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); + assertThrows(UnsupportedOperationException.class, + () -> executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases()))); + } + + @Test + void assertWhereIdFilterCaseInsensitive() { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("ID", 1001L); + sqlStatement.setWhere(whereSegment); + MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); + assertThrows(UnsupportedOperationException.class, + () -> executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases()))); + } + + @Test + void assertWhereLabelFilterCaseInsensitive() { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("LABEL", "test_label"); + sqlStatement.setWhere(whereSegment); + MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); + assertThrows(UnsupportedOperationException.class, + () -> executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases()))); + } + + @Test + void assertQueryResultMetaDataWithFullColumnSet() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); + executor.setTestData(Collections.emptyList()); + executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); + QueryResultMetaData metaData = executor.getQueryResultMetaData(); assertThat(metaData.getColumnCount(), is(12)); assertThat(metaData.getColumnLabel(1), is("TransactionId")); assertThat(metaData.getColumnType(1), is(Types.BIGINT)); @@ -209,6 +232,131 @@ private void assertQueryResultMetaData(final QueryResultMetaData metaData) throw assertThat(metaData.getColumnType(12), is(Types.BIGINT)); } + @Test + void assertExecuteWithDataPresent() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); + executor.setTestData(createMockTransactions()); + executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); + MergedResult mergedResult = executor.getMergedResult(); + assertTrue(mergedResult.next()); + assertThat(mergedResult.getValue(1, String.class), is("1001")); + assertThat(mergedResult.getValue(2, String.class), is("load_job_1")); + assertThat(mergedResult.getValue(3, String.class), is("coordinator_node_1")); + assertThat(mergedResult.getValue(4, String.class), is("VISIBLE")); + assertTrue(mergedResult.next()); + assertThat(mergedResult.getValue(1, String.class), is("1002")); + assertThat(mergedResult.getValue(2, String.class), is("load_job_2")); + assertFalse(mergedResult.next()); + } + + @Test + void assertExecuteWithDataAbsent() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); + executor.setTestData(Collections.emptyList()); + executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); + MergedResult mergedResult = executor.getMergedResult(); + assertFalse(mergedResult.next()); + } + + @Test + void assertWhereIdFilterHit() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("id", 1001L); + sqlStatement.setWhere(whereSegment); + TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); + executor.setTestData(createMockTransactions()); + executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); + MergedResult mergedResult = executor.getMergedResult(); + assertTrue(mergedResult.next()); + assertThat(mergedResult.getValue(1, String.class), is("1001")); + assertThat(mergedResult.getValue(2, String.class), is("load_job_1")); + assertFalse(mergedResult.next()); + } + + @Test + void assertWhereIdFilterMiss() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("id", 9999L); + sqlStatement.setWhere(whereSegment); + TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); + executor.setTestData(createMockTransactions()); + executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); + MergedResult mergedResult = executor.getMergedResult(); + assertFalse(mergedResult.next()); + } + + @Test + void assertWhereLabelFilterHit() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("label", "load_job_2"); + sqlStatement.setWhere(whereSegment); + TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); + executor.setTestData(createMockTransactions()); + executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); + MergedResult mergedResult = executor.getMergedResult(); + assertTrue(mergedResult.next()); + assertThat(mergedResult.getValue(1, String.class), is("1002")); + assertThat(mergedResult.getValue(2, String.class), is("load_job_2")); + assertFalse(mergedResult.next()); + } + + @Test + void assertWhereLabelFilterMiss() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("label", "non_existent_label"); + sqlStatement.setWhere(whereSegment); + TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); + executor.setTestData(createMockTransactions()); + executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); + MergedResult mergedResult = executor.getMergedResult(); + assertFalse(mergedResult.next()); + } + + @Test + void assertWhereIdWithStringLiteralFilterHit() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("id", "1002"); + sqlStatement.setWhere(whereSegment); + TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); + executor.setTestData(createMockTransactions()); + executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); + MergedResult mergedResult = executor.getMergedResult(); + assertTrue(mergedResult.next()); + assertThat(mergedResult.getValue(1, String.class), is("1002")); + assertFalse(mergedResult.next()); + } + + @Test + void assertWhereIdCaseInsensitiveFilterHit() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("ID", 1001L); + sqlStatement.setWhere(whereSegment); + TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); + executor.setTestData(createMockTransactions()); + executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); + MergedResult mergedResult = executor.getMergedResult(); + assertTrue(mergedResult.next()); + assertThat(mergedResult.getValue(1, String.class), is("1001")); + assertFalse(mergedResult.next()); + } + + @Test + void assertWhereLabelCaseInsensitiveFilterHit() throws SQLException { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("LABEL", "load_job_1"); + sqlStatement.setWhere(whereSegment); + TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); + executor.setTestData(createMockTransactions()); + executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); + MergedResult mergedResult = executor.getMergedResult(); + assertTrue(mergedResult.next()); + assertThat(mergedResult.getValue(1, String.class), is("1001")); + assertThat(mergedResult.getValue(2, String.class), is("load_job_1")); + assertFalse(mergedResult.next()); + } + private WhereSegment createWhereSegment(final String columnName, final Object value) { ColumnSegment columnSegment = new ColumnSegment(0, 0, new IdentifierValue(columnName)); LiteralExpressionSegment literalSegment = new LiteralExpressionSegment(0, 0, value); @@ -234,4 +382,41 @@ private ConnectionSession mockConnectionSession(final String usedDatabaseName) { when(result.getUsedDatabaseName()).thenReturn(usedDatabaseName); return result; } + + private Collection createMockTransactions() { + List mockTransactions = new ArrayList<>(2); + mockTransactions.add(new MySQLShowTransactionExecutor.TransactionInfo( + 1001L, "load_job_1", "coordinator_node_1", "VISIBLE", "ROUTINE_LOAD", + "2025-01-01 10:00:00", "2025-01-01 10:01:00", "2025-01-01 10:02:00", + "", 0, 10001L, 60000L)); + mockTransactions.add(new MySQLShowTransactionExecutor.TransactionInfo( + 1002L, "load_job_2", "coordinator_node_2", "COMMITTED", "BROKER_LOAD", + "2025-01-01 11:00:00", "2025-01-01 11:01:00", "", + "", 0, 10002L, 120000L)); + return mockTransactions; + } + + /** + * Testable executor for testing data flow and filtering logic. + */ + static class TestableShowTransactionExecutor extends MySQLShowTransactionExecutor { + + private Collection testData; + + TestableShowTransactionExecutor(final MySQLShowTransactionStatement sqlStatement) { + super(sqlStatement); + } + + void setTestData(final Collection data) { + this.testData = data; + } + + @Override + protected Collection loadTransactions() { + if (null != testData) { + return testData; + } + return super.loadTransactions(); + } + } } From 1cb29b46a0be703d0aee9acce8a7f7a5cd9bb1cb Mon Sep 17 00:00:00 2001 From: xiaosaxiaozai Date: Mon, 22 Dec 2025 15:59:57 +0800 Subject: [PATCH 6/6] Support for the SHOW TRANSACTION statement and the L2_DISTANCE function --- .../show/MySQLShowTransactionExecutor.java | 26 ++++--- .../MySQLShowTransactionExecutorTest.java | 70 +++++++++++++++++-- 2 files changed, 79 insertions(+), 17 deletions(-) diff --git a/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java b/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java index eefd39654a733..bb9bb19787fd4 100644 --- a/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java +++ b/proxy/backend/dialect/mysql/src/main/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutor.java @@ -110,27 +110,33 @@ private Collection getQueryResultRows(final String data } private void extractWhereFilter() { - if (null == sqlStatement.getWhere()) { - return; - } - if (sqlStatement.getWhere().getExpr() instanceof BinaryOperationExpression) { - BinaryOperationExpression binaryExpr = (BinaryOperationExpression) sqlStatement.getWhere().getExpr(); - extractFilterFromBinaryExpression(binaryExpr); - } + ShardingSpherePreconditions.checkState(null != sqlStatement.getWhere(), + () -> new IllegalArgumentException("SHOW TRANSACTION requires WHERE clause with 'id = ' or 'label = '")); + ShardingSpherePreconditions.checkState(sqlStatement.getWhere().getExpr() instanceof BinaryOperationExpression, + () -> new IllegalArgumentException("WHERE clause must be in the form: id = or label = ")); + BinaryOperationExpression binaryExpr = (BinaryOperationExpression) sqlStatement.getWhere().getExpr(); + extractFilterFromBinaryExpression(binaryExpr); + ShardingSpherePreconditions.checkState(filterTransactionId.isPresent() || filterLabel.isPresent(), + () -> new IllegalArgumentException("WHERE clause must specify either 'id = ' or 'label = '")); } private void extractFilterFromBinaryExpression(final BinaryOperationExpression expression) { - if (!(expression.getLeft() instanceof ColumnSegment) || !(expression.getRight() instanceof LiteralExpressionSegment)) { - return; - } + ShardingSpherePreconditions.checkState(expression.getLeft() instanceof ColumnSegment && expression.getRight() instanceof LiteralExpressionSegment, + () -> new IllegalArgumentException("WHERE clause must be in the form: column = literal")); + ShardingSpherePreconditions.checkState("=".equals(expression.getOperator()), + () -> new IllegalArgumentException("WHERE clause only supports '=' operator, got: " + expression.getOperator())); ColumnSegment column = (ColumnSegment) expression.getLeft(); LiteralExpressionSegment literal = (LiteralExpressionSegment) expression.getRight(); String columnName = column.getIdentifier().getValue().toLowerCase(); Object literalValue = literal.getLiterals(); if ("id".equalsIgnoreCase(columnName)) { filterTransactionId = extractLongValue(literalValue); + ShardingSpherePreconditions.checkState(filterTransactionId.isPresent(), + () -> new IllegalArgumentException("Invalid transaction id value: " + literalValue)); } else if ("label".equalsIgnoreCase(columnName)) { filterLabel = Optional.of(String.valueOf(literalValue)); + } else { + throw new IllegalArgumentException("WHERE clause only supports 'id' or 'label' columns, got: " + columnName); } } diff --git a/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java b/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java index 7a74ce54a58bf..09dff5fba64d8 100644 --- a/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java +++ b/proxy/backend/dialect/mysql/src/test/java/org/apache/shardingsphere/proxy/backend/mysql/handler/admin/executor/show/MySQLShowTransactionExecutorTest.java @@ -73,9 +73,9 @@ class MySQLShowTransactionExecutorTest { void assertExecuteWithoutFromAndWhere() { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); - UnsupportedOperationException exception = assertThrows(UnsupportedOperationException.class, + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases()))); - assertThat(exception.getMessage(), is("SHOW TRANSACTION is not supported for the moment. ")); + assertThat(exception.getMessage(), is("SHOW TRANSACTION requires WHERE clause with 'id = ' or 'label = '")); } @Test @@ -84,7 +84,7 @@ void assertExecuteWithFromDatabase() { FromDatabaseSegment fromDatabaseSegment = new FromDatabaseSegment(0, new DatabaseSegment(0, 0, new IdentifierValue(DATABASE_NAME))); sqlStatement.setFromDatabase(fromDatabaseSegment); MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); - assertThrows(UnsupportedOperationException.class, + assertThrows(IllegalArgumentException.class, () -> executor.execute(mockConnectionSession("other_db"), mockMetaData(createDatabases()))); } @@ -137,6 +137,8 @@ void assertExecuteWithUnknownDatabase() { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); FromDatabaseSegment fromDatabaseSegment = new FromDatabaseSegment(0, new DatabaseSegment(0, 0, new IdentifierValue("unknown_db"))); sqlStatement.setFromDatabase(fromDatabaseSegment); + WhereSegment whereSegment = createWhereSegment("id", 1001L); + sqlStatement.setWhere(whereSegment); MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); assertThrows(UnknownDatabaseException.class, () -> executor.execute(mockConnectionSession("other_db"), mockMetaData(createDatabases()))); } @@ -146,6 +148,8 @@ void assertExecuteWithUncompletedDatabase() { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); FromDatabaseSegment fromDatabaseSegment = new FromDatabaseSegment(0, new DatabaseSegment(0, 0, new IdentifierValue("uncompleted"))); sqlStatement.setFromDatabase(fromDatabaseSegment); + WhereSegment whereSegment = createWhereSegment("id", 1001L); + sqlStatement.setWhere(whereSegment); MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); executor.execute(mockConnectionSession("uncompleted"), mockMetaData(createDatabases())); } @@ -154,7 +158,7 @@ void assertExecuteWithUncompletedDatabase() { void assertTransactionStatusConstants() { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); MySQLShowTransactionExecutor executor = new MySQLShowTransactionExecutor(sqlStatement); - assertThrows(UnsupportedOperationException.class, + assertThrows(IllegalArgumentException.class, () -> executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases()))); } @@ -201,6 +205,8 @@ void assertWhereLabelFilterCaseInsensitive() { @Test void assertQueryResultMetaDataWithFullColumnSet() throws SQLException { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("id", 1001L); + sqlStatement.setWhere(whereSegment); TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); executor.setTestData(Collections.emptyList()); executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); @@ -235,6 +241,8 @@ void assertQueryResultMetaDataWithFullColumnSet() throws SQLException { @Test void assertExecuteWithDataPresent() throws SQLException { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("id", 1001L); + sqlStatement.setWhere(whereSegment); TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); executor.setTestData(createMockTransactions()); executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); @@ -244,15 +252,14 @@ void assertExecuteWithDataPresent() throws SQLException { assertThat(mergedResult.getValue(2, String.class), is("load_job_1")); assertThat(mergedResult.getValue(3, String.class), is("coordinator_node_1")); assertThat(mergedResult.getValue(4, String.class), is("VISIBLE")); - assertTrue(mergedResult.next()); - assertThat(mergedResult.getValue(1, String.class), is("1002")); - assertThat(mergedResult.getValue(2, String.class), is("load_job_2")); assertFalse(mergedResult.next()); } @Test void assertExecuteWithDataAbsent() throws SQLException { MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("id", 1001L); + sqlStatement.setWhere(whereSegment); TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); executor.setTestData(Collections.emptyList()); executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases())); @@ -357,6 +364,55 @@ void assertWhereLabelCaseInsensitiveFilterHit() throws SQLException { assertFalse(mergedResult.next()); } + @Test + void assertMissingWhereClause() { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); + executor.setTestData(createMockTransactions()); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases()))); + assertThat(exception.getMessage(), is("SHOW TRANSACTION requires WHERE clause with 'id = ' or 'label = '")); + } + + @Test + void assertWhereWithUnsupportedColumn() { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("status", "VISIBLE"); + sqlStatement.setWhere(whereSegment); + TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); + executor.setTestData(createMockTransactions()); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases()))); + assertThat(exception.getMessage(), is("WHERE clause only supports 'id' or 'label' columns, got: status")); + } + + @Test + void assertWhereWithInvalidIdValue() { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + WhereSegment whereSegment = createWhereSegment("id", "not_a_number"); + sqlStatement.setWhere(whereSegment); + TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); + executor.setTestData(createMockTransactions()); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases()))); + assertThat(exception.getMessage(), is("Invalid transaction id value: not_a_number")); + } + + @Test + void assertWhereWithNonEqualityOperator() { + MySQLShowTransactionStatement sqlStatement = new MySQLShowTransactionStatement(databaseType); + ColumnSegment columnSegment = new ColumnSegment(0, 0, new IdentifierValue("id")); + LiteralExpressionSegment literalSegment = new LiteralExpressionSegment(0, 0, 1000L); + BinaryOperationExpression expression = new BinaryOperationExpression(0, 0, columnSegment, literalSegment, ">", ""); + WhereSegment whereSegment = new WhereSegment(0, 0, expression); + sqlStatement.setWhere(whereSegment); + TestableShowTransactionExecutor executor = new TestableShowTransactionExecutor(sqlStatement); + executor.setTestData(createMockTransactions()); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> executor.execute(mockConnectionSession(DATABASE_NAME), mockMetaData(createDatabases()))); + assertThat(exception.getMessage(), is("WHERE clause only supports '=' operator, got: >")); + } + private WhereSegment createWhereSegment(final String columnName, final Object value) { ColumnSegment columnSegment = new ColumnSegment(0, 0, new IdentifierValue(columnName)); LiteralExpressionSegment literalSegment = new LiteralExpressionSegment(0, 0, value);