This is an automated email from the ASF dual-hosted git repository.
strongduanmu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new 499c89a807e Refactor SQLRewriterIT to test more sql rewrite case
(#38700)
499c89a807e is described below
commit 499c89a807e43b147ccf3f9ee77ace8ca086e39e
Author: Zhengqiang Duan <[email protected]>
AuthorDate: Mon May 18 12:15:28 2026 +0800
Refactor SQLRewriterIT to test more sql rewrite case (#38700)
---
AGENTS.md | 6 +-
.../test/it/rewriter/engine/SQLRewriterIT.java | 98 ++++++++---
.../mocker/DialectStorageUnitMetaDataMocker.java} | 29 ++--
.../type/MySQLStorageUnitMetaDataMocker.java | 110 ++++++++++++
.../SQLRewriteEngineTestParametersBuilder.java | 184 +++++++++++++++++++--
.../it/rewriter/entity/RewriteAssertionEntity.java | 3 +
.../it/rewriter/entity/RewriteInputEntity.java | 3 +
.../it/rewriter/entity/RewriteOutputEntity.java | 3 +
....engine.mocker.DialectStorageUnitMetaDataMocker | 18 ++
9 files changed, 403 insertions(+), 51 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index b14093cf1fe..1be4830d797 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -32,6 +32,7 @@ This guide is written **for AI coding agents only**. Follow
it literally; improv
- **Test-Driven**: design for testability, ensure unit-test coverage, and keep
background unit tests under 60s to avoid job stalls.
- **Quality Assurance**: run static checks, formatting, and code reviews.
- **Checkstyle Gate**: do not hand off code with Checkstyle/Spotless
failures—run the relevant module check locally and fix before completion.
+- **Formatting Gate**: after code changes, format only with `./mvnw
spotless:apply -Pcheck -T1C`, then check style with `./mvnw checkstyle:check
-Pcheck -T1C`; do not use any other formatting method.
- **Continuous Verification**: rely on automated tests and integration
validation.
- **Test Naming Simplicity**: keep test names concise and scenario-focused
(avoid “ReturnsXXX”/overly wordy or AI-like phrasing); describe the scenario
directly.
- **Coverage Discipline**: follow the dedicated coverage & branch checklist
before coding when coverage targets are stated.
@@ -185,9 +186,10 @@ Always state which topology, registry, and engine versions
(e.g., MySQL 5.7 vs 8
- **Success recipe:** explain why the change exists, cite the affected
data-flow step, keep public APIs backward compatible, and record defaults/knobs
alongside code changes.
## Verification & Commands
-- Core commands: `./mvnw clean install -B -T1C -Pcheck` (full build), `./mvnw
test -pl <module>[-am]` (scoped unit tests), `./mvnw spotless:apply -Pcheck
[-pl <module>]` (format), `./mvnw -pl <module> -DskipITs -Dspotless.skip=true
-Dtest=ClassName test` (fast verification), `./mvnw -pl proxy -am -DskipTests
package` (proxy packaging/perf smoke).
+- Core commands: `./mvnw clean install -B -T1C -Pcheck` (full build), `./mvnw
test -pl <module>[-am]` (scoped unit tests), `./mvnw -pl <module> -DskipITs
-Dspotless.skip=true -Dtest=ClassName test` (fast verification), `./mvnw -pl
proxy -am -DskipTests package` (proxy packaging/perf smoke).
- Coverage: when tests change or targets demand it, run `./mvnw test
jacoco:check@jacoco-check -Pcoverage-check` or scoped `-pl <module> -am
-Djacoco.skip=false test jacoco:report`; pair with the Coverage & Branch
Checklist.
-- Style: `./mvnw checkstyle:check -Pcheck` (scoped with `-pl <module> -am
-Pcheck` when possible) unless told otherwise.
+- Format: after code changes, run `./mvnw spotless:apply -Pcheck -T1C`; do not
use any other formatting method.
+- Style: after code changes and formatting, run `./mvnw checkstyle:check
-Pcheck -T1C`.
- Scoped defaults: prefer module-scoped runs over whole-repo builds; include
`-Dsurefire.failIfNoSpecifiedTests=false` when targeting specific tests.
- Testing ground rules: JUnit 5 + Mockito, `ClassNameTest` naming,
Arrange–Act–Assert, mock external systems/time/network, reset static caches,
and reuse swappers/helpers for complex configs.
- API bans: if a user forbids a tool/assertion, add it to the plan, avoid it
during implementation, and cite verification searches (e.g., `rg assertEquals`)
in the final report.
diff --git
a/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/engine/SQLRewriterIT.java
b/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/engine/SQLRewriterIT.java
index 9b393c94e2c..bb7630ee164 100644
---
a/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/engine/SQLRewriterIT.java
+++
b/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/engine/SQLRewriterIT.java
@@ -19,6 +19,8 @@ package org.apache.shardingsphere.test.it.rewriter.engine;
import com.google.common.base.Preconditions;
import org.apache.shardingsphere.database.connector.core.DefaultDatabase;
+import
org.apache.shardingsphere.database.connector.core.jdbcurl.parser.ConnectionProperties;
+import
org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
import
org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
import org.apache.shardingsphere.infra.binder.context.aware.ParameterAware;
@@ -29,11 +31,15 @@ import
org.apache.shardingsphere.infra.binder.engine.SQLBindEngine;
import org.apache.shardingsphere.infra.config.database.DatabaseConfiguration;
import
org.apache.shardingsphere.infra.config.database.impl.DataSourceProvidedDatabaseConfiguration;
import org.apache.shardingsphere.infra.config.props.ConfigurationProperties;
+import org.apache.shardingsphere.infra.config.props.ConfigurationPropertyKey;
+import
org.apache.shardingsphere.infra.config.props.MetadataIdentifierCaseSensitivity;
import org.apache.shardingsphere.infra.hint.HintValueContext;
import org.apache.shardingsphere.infra.hint.SQLHintUtils;
+import org.apache.shardingsphere.infra.instance.ComputeNodeInstanceContext;
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.resource.unit.StorageUnit;
import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
import
org.apache.shardingsphere.infra.metadata.database.schema.model.ShardingSphereSchema;
import org.apache.shardingsphere.infra.parser.SQLParserEngine;
@@ -57,6 +63,7 @@ import
org.apache.shardingsphere.infra.yaml.config.swapper.rule.YamlRuleConfigur
import org.apache.shardingsphere.parser.rule.SQLParserRule;
import
org.apache.shardingsphere.parser.rule.builder.DefaultSQLParserRuleConfigurationBuilder;
import
org.apache.shardingsphere.sql.parser.statement.core.statement.SQLStatement;
+import
org.apache.shardingsphere.test.it.rewriter.engine.mocker.DialectStorageUnitMetaDataMocker;
import
org.apache.shardingsphere.test.it.rewriter.engine.parameter.SQLRewriteEngineTestParameters;
import
org.apache.shardingsphere.test.it.rewriter.engine.parameter.SQLRewriteEngineTestParametersBuilder;
import org.junit.jupiter.api.extension.ExtensionContext;
@@ -66,24 +73,33 @@ import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;
import org.junit.jupiter.params.support.ParameterDeclarations;
+import javax.sql.DataSource;
import java.io.File;
import java.io.IOException;
import java.net.URL;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Properties;
import java.util.stream.Stream;
-import static org.hamcrest.Matchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public abstract class SQLRewriterIT {
@ParameterizedTest(name = "{0}")
@ArgumentsSource(TestCaseArgumentsProvider.class)
- void assertRewrite(final SQLRewriteEngineTestParameters testParams) throws
IOException {
+ void assertRewrite(final SQLRewriteEngineTestParameters testParams) throws
IOException, SQLException {
Collection<SQLRewriteUnit> actual = createSQLRewriteUnits(testParams);
assertThat(actual.size(), is(testParams.getOutputSQLs().size()));
int count = 0;
@@ -97,45 +113,76 @@ public abstract class SQLRewriterIT {
}
}
- private Collection<SQLRewriteUnit> createSQLRewriteUnits(final
SQLRewriteEngineTestParameters testParams) throws IOException {
+ private Collection<SQLRewriteUnit> createSQLRewriteUnits(final
SQLRewriteEngineTestParameters testParams) throws IOException, SQLException {
YamlRootConfiguration rootConfig = loadRootConfiguration(testParams);
- DatabaseConfiguration databaseConfig = new
DataSourceProvidedDatabaseConfiguration(
- new
YamlDataSourceConfigurationSwapper().swapToDataSources(rootConfig.getDataSources()),
new
YamlRuleConfigurationSwapperEngine().swapToRuleConfigurations(rootConfig.getRules()));
DatabaseType databaseType =
TypedSPILoader.getService(DatabaseType.class, testParams.getDatabaseType());
- ResourceMetaData resourceMetaData = new
ResourceMetaData(Collections.emptyMap(), databaseConfig.getStorageUnits());
- String databaseName = null == rootConfig.getDatabaseName() ?
DefaultDatabase.LOGIC_NAME : rootConfig.getDatabaseName();
- String schemaName = new
DatabaseTypeRegistry(databaseType).getDefaultSchemaName(databaseName);
String sql = SQLHintUtils.removeHint(testParams.getInputSQL());
SQLParserEngine sqlParserEngine = new SQLParserRule(new
DefaultSQLParserRuleConfigurationBuilder().build()).getSQLParserEngine(databaseType);
SQLStatement sqlStatement = sqlParserEngine.parse(sql, false);
- Collection<ShardingSphereRule> rules =
createDatabaseRules(databaseConfig, schemaName, sqlStatement, databaseType);
- ShardingSphereDatabase database =
- new ShardingSphereDatabase(databaseName, databaseType,
resourceMetaData, new RuleMetaData(rules), mockSchemas(schemaName), new
ConfigurationProperties(new Properties()));
+ ShardingSphereDatabase database = createDatabase(rootConfig,
sqlStatement, databaseType);
RuleMetaData globalRuleMetaData = new
RuleMetaData(GlobalRulesBuilder.buildRules(Collections.emptyList(),
Collections.emptyList(), new ConfigurationProperties(new Properties())));
- ConfigurationProperties props = new
ConfigurationProperties(rootConfig.getProps());
+ ConfigurationProperties props =
createConfigurationProperties(rootConfig);
ShardingSphereMetaData metaData = new
ShardingSphereMetaData(Collections.singleton(database), mock(),
globalRuleMetaData, props);
HintValueContext hintValueContext =
SQLHintUtils.extractHint(testParams.getInputSQL());
- SQLStatementContext sqlStatementContext = bind(testParams, metaData,
databaseName, hintValueContext, sqlStatement, sqlParserEngine);
+ SQLStatementContext sqlStatementContext = bind(testParams, metaData,
database.getName(), hintValueContext, sqlStatement, sqlParserEngine);
ConnectionContext connectionContext =
createConnectionContext(database.getName());
QueryContext queryContext = new QueryContext(sqlStatementContext, sql,
testParams.getInputParameters(), hintValueContext, connectionContext, metaData);
+ Collection<ShardingSphereRule> rules =
database.getRuleMetaData().getRules();
RouteContext routeContext = new SQLRouteEngine(rules,
props).route(queryContext, globalRuleMetaData, database);
- SQLRewriteEntry sqlRewriteEntry = new SQLRewriteEntry(database,
globalRuleMetaData, props);
- SQLRewriteResult sqlRewriteResult =
sqlRewriteEntry.rewrite(queryContext, routeContext);
+ SQLRewriteResult sqlRewriteResult = new SQLRewriteEntry(database,
globalRuleMetaData, props).rewrite(queryContext, routeContext);
+ return createSQLRewriteUnits(sqlRewriteResult);
+ }
+
+ private Collection<SQLRewriteUnit> createSQLRewriteUnits(final
SQLRewriteResult sqlRewriteResult) {
return sqlRewriteResult instanceof GenericSQLRewriteResult
? Collections.singleton(((GenericSQLRewriteResult)
sqlRewriteResult).getSqlRewriteUnit())
: (((RouteSQLRewriteResult)
sqlRewriteResult).getSqlRewriteUnits()).values();
}
- private YamlRootConfiguration loadRootConfiguration(final
SQLRewriteEngineTestParameters testParams) throws IOException {
- URL url =
Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource(testParams.getRuleFile()),
- String.format("Can not find configuration file `%s`",
testParams.getRuleFile()));
+ protected final YamlRootConfiguration loadRootConfiguration(final
SQLRewriteEngineTestParameters testParams) throws IOException {
+ return loadRootConfiguration(testParams.getRuleFile());
+ }
+
+ protected final YamlRootConfiguration loadRootConfiguration(final String
ruleFile) throws IOException {
+ URL url =
Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource(ruleFile),
String.format("Can not find configuration file `%s`", ruleFile));
return YamlEngine.unmarshal(new File(url.getFile()),
YamlRootConfiguration.class);
}
- private Collection<ShardingSphereRule> createDatabaseRules(final
DatabaseConfiguration databaseConfig, final String schemaName, final
SQLStatement sqlStatement, final DatabaseType databaseType) {
- Collection<ShardingSphereRule> result = DatabaseRulesBuilder.build(
- DefaultDatabase.LOGIC_NAME, databaseType, databaseConfig,
mock(), new ResourceMetaData(databaseConfig.getDataSources(),
databaseConfig.getStorageUnits()));
- mockDatabaseRules(result, schemaName, sqlStatement);
+ private ShardingSphereDatabase createDatabase(final YamlRootConfiguration
rootConfig, final SQLStatement sqlStatement, final DatabaseType databaseType)
throws SQLException {
+ DatabaseConfiguration databaseConfig = new
DataSourceProvidedDatabaseConfiguration(
+ new
YamlDataSourceConfigurationSwapper().swapToDataSources(rootConfig.getDataSources()),
new
YamlRuleConfigurationSwapperEngine().swapToRuleConfigurations(rootConfig.getRules()));
+ ResourceMetaData resourceMetaData = new
ResourceMetaData(Collections.emptyMap(), createStorageUnits(databaseConfig,
databaseType));
+ String databaseName = null == rootConfig.getDatabaseName() ?
DefaultDatabase.LOGIC_NAME : rootConfig.getDatabaseName();
+ String schemaName = new
DatabaseTypeRegistry(databaseType).getDefaultSchemaName(databaseName);
+ Collection<ShardingSphereRule> rules =
createDatabaseRules(databaseConfig, schemaName, sqlStatement, databaseType);
+ ConfigurationProperties props =
createConfigurationProperties(rootConfig);
+ RuleMetaData ruleMetaData = new RuleMetaData(rules);
+ Collection<ShardingSphereSchema> schemas = mockSchemas(schemaName);
+ return new ShardingSphereDatabase(databaseName, databaseType,
resourceMetaData, ruleMetaData, schemas, props);
+ }
+
+ private ConfigurationProperties createConfigurationProperties(final
YamlRootConfiguration rootConfig) {
+ Properties result = new Properties();
+ result.putAll(rootConfig.getProps());
+
result.putIfAbsent(ConfigurationPropertyKey.METADATA_IDENTIFIER_CASE_SENSITIVITY.getKey(),
MetadataIdentifierCaseSensitivity.INSENSITIVE.name());
+ return new ConfigurationProperties(result);
+ }
+
+ private Map<String, StorageUnit> createStorageUnits(final
DatabaseConfiguration databaseConfig, final DatabaseType databaseType) throws
SQLException {
+ Map<String, StorageUnit> result = new
LinkedHashMap<>(databaseConfig.getStorageUnits().size(), 1F);
+ for (Entry<String, StorageUnit> entry :
databaseConfig.getStorageUnits().entrySet()) {
+ StorageUnit storageUnit = mock(StorageUnit.class,
RETURNS_DEEP_STUBS);
+ DataSource dataSource = mock(DataSource.class);
+ when(storageUnit.getStorageType()).thenReturn(databaseType);
+ when(storageUnit.getDataSource()).thenReturn(dataSource);
+ when(storageUnit.getConnectionProperties()).thenReturn(new
ConnectionProperties("127.0.0.1", 3306, entry.getKey(), null, new
Properties()));
+ Connection connection = mock(Connection.class);
+ DatabaseMetaData databaseMetaData = mock(DatabaseMetaData.class);
+ when(dataSource.getConnection()).thenReturn(connection);
+ when(connection.getMetaData()).thenReturn(databaseMetaData);
+
DatabaseTypedSPILoader.findService(DialectStorageUnitMetaDataMocker.class,
databaseType).ifPresent(optional ->
optional.mockStorageUnitMetaData(connection, databaseMetaData));
+ result.put(entry.getKey(), storageUnit);
+ }
return result;
}
@@ -162,6 +209,13 @@ public abstract class SQLRewriterIT {
return result;
}
+ private Collection<ShardingSphereRule> createDatabaseRules(final
DatabaseConfiguration databaseConfig, final String schemaName, final
SQLStatement sqlStatement, final DatabaseType databaseType) {
+ Collection<ShardingSphereRule> result =
DatabaseRulesBuilder.build(DefaultDatabase.LOGIC_NAME, databaseType,
+ databaseConfig, mock(ComputeNodeInstanceContext.class), new
ResourceMetaData(databaseConfig.getDataSources(),
databaseConfig.getStorageUnits()));
+ mockDatabaseRules(result, schemaName, sqlStatement);
+ return result;
+ }
+
protected abstract Collection<ShardingSphereSchema> mockSchemas(String
schemaName);
protected abstract void mockDatabaseRules(Collection<ShardingSphereRule>
rules, String schemaName, SQLStatement sqlStatement);
diff --git
a/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteInputEntity.java
b/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/engine/mocker/DialectStorageUnitMetaDataMocker.java
similarity index 59%
copy from
test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteInputEntity.java
copy to
test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/engine/mocker/DialectStorageUnitMetaDataMocker.java
index cc9a4665c06..2b5652a1d55 100644
---
a/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteInputEntity.java
+++
b/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/engine/mocker/DialectStorageUnitMetaDataMocker.java
@@ -15,26 +15,23 @@
* limitations under the License.
*/
-package org.apache.shardingsphere.test.it.rewriter.entity;
+package org.apache.shardingsphere.test.it.rewriter.engine.mocker;
-import lombok.Getter;
-import lombok.Setter;
+import org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPI;
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlAttribute;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
/**
- * Rewrite input entity for JAXB.
+ * Dialect storage unit meta data mocker.
*/
-@XmlAccessorType(XmlAccessType.FIELD)
-@Getter
-@Setter
-public final class RewriteInputEntity {
+public interface DialectStorageUnitMetaDataMocker extends DatabaseTypedSPI {
- @XmlAttribute(required = true)
- private String sql;
-
- @XmlAttribute
- private String parameters;
+ /**
+ * Mock storage unit meta data.
+ *
+ * @param connection connection
+ * @param databaseMetaData database meta data
+ */
+ void mockStorageUnitMetaData(Connection connection, DatabaseMetaData
databaseMetaData);
}
diff --git
a/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/engine/mocker/type/MySQLStorageUnitMetaDataMocker.java
b/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/engine/mocker/type/MySQLStorageUnitMetaDataMocker.java
new file mode 100644
index 00000000000..75c8d401a18
--- /dev/null
+++
b/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/engine/mocker/type/MySQLStorageUnitMetaDataMocker.java
@@ -0,0 +1,110 @@
+/*
+ * 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.rewriter.engine.mocker.type;
+
+import lombok.SneakyThrows;
+import
org.apache.shardingsphere.test.it.rewriter.engine.mocker.DialectStorageUnitMetaDataMocker;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * MySQL storage unit meta data mocker.
+ */
+public final class MySQLStorageUnitMetaDataMocker implements
DialectStorageUnitMetaDataMocker {
+
+ @Override
+ @SneakyThrows(SQLException.class)
+ public void mockStorageUnitMetaData(final Connection connection, final
DatabaseMetaData databaseMetaData) {
+ mockIdentifierCaseRule(connection);
+ mockDataTypeInfo(databaseMetaData);
+ }
+
+ private void mockIdentifierCaseRule(final Connection connection) throws
SQLException {
+ PreparedStatement preparedStatement = mock(PreparedStatement.class);
+ ResultSet resultSet = mock(ResultSet.class);
+ when(connection.prepareStatement("SELECT
@@lower_case_table_names")).thenReturn(preparedStatement);
+ when(preparedStatement.executeQuery()).thenReturn(resultSet);
+ when(resultSet.next()).thenReturn(true);
+ when(resultSet.getInt(1)).thenReturn(0);
+ }
+
+ private void mockDataTypeInfo(final DatabaseMetaData databaseMetaData)
throws SQLException {
+ Map<String, Integer> dataTypes = new LinkedHashMap<>(30, 1F);
+ dataTypes.put("TINYINT", Types.TINYINT);
+ dataTypes.put("SMALLINT", Types.SMALLINT);
+ dataTypes.put("MEDIUMINT", Types.INTEGER);
+ dataTypes.put("INT", Types.INTEGER);
+ dataTypes.put("INTEGER", Types.INTEGER);
+ dataTypes.put("BIGINT", Types.BIGINT);
+ dataTypes.put("DECIMAL", Types.DECIMAL);
+ dataTypes.put("NUMERIC", Types.NUMERIC);
+ dataTypes.put("FLOAT", Types.FLOAT);
+ dataTypes.put("REAL", Types.REAL);
+ dataTypes.put("DOUBLE", Types.DOUBLE);
+ dataTypes.put("BIT", Types.BIT);
+ dataTypes.put("DATE", Types.DATE);
+ dataTypes.put("DATETIME", Types.TIMESTAMP);
+ dataTypes.put("TIMESTAMP", Types.TIMESTAMP);
+ dataTypes.put("TIME", Types.TIME);
+ dataTypes.put("CHAR", Types.CHAR);
+ dataTypes.put("VARCHAR", Types.VARCHAR);
+ dataTypes.put("BINARY", Types.BINARY);
+ dataTypes.put("VARBINARY", Types.VARBINARY);
+ dataTypes.put("TINYBLOB", Types.BLOB);
+ dataTypes.put("BLOB", Types.BLOB);
+ dataTypes.put("MEDIUMBLOB", Types.BLOB);
+ dataTypes.put("LONGBLOB", Types.BLOB);
+ dataTypes.put("TINYTEXT", Types.LONGVARCHAR);
+ dataTypes.put("TEXT", Types.LONGVARCHAR);
+ dataTypes.put("MEDIUMTEXT", Types.LONGVARCHAR);
+ dataTypes.put("LONGTEXT", Types.LONGVARCHAR);
+ dataTypes.put("ENUM", Types.VARCHAR);
+ dataTypes.put("SET", Types.VARCHAR);
+ ResultSet resultSet = mock(ResultSet.class);
+ AtomicReference<Entry<String, Integer>> current = new
AtomicReference<>();
+ when(resultSet.next()).thenAnswer(invocation -> {
+ if (dataTypes.isEmpty()) {
+ return false;
+ }
+ Entry<String, Integer> next =
dataTypes.entrySet().iterator().next();
+ dataTypes.remove(next.getKey());
+ current.set(next);
+ return true;
+ });
+ when(resultSet.getString("TYPE_NAME")).thenAnswer(invocation ->
current.get().getKey());
+ when(resultSet.getInt("DATA_TYPE")).thenAnswer(invocation ->
current.get().getValue());
+ when(databaseMetaData.getTypeInfo()).thenReturn(resultSet);
+ }
+
+ @Override
+ public String getDatabaseType() {
+ return "MySQL";
+ }
+}
diff --git
a/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/engine/parameter/SQLRewriteEngineTestParametersBuilder.java
b/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/engine/parameter/SQLRewriteEngineTestParametersBuilder.java
index c07db3f5edc..a866790d989 100644
---
a/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/engine/parameter/SQLRewriteEngineTestParametersBuilder.java
+++
b/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/engine/parameter/SQLRewriteEngineTestParametersBuilder.java
@@ -21,8 +21,9 @@ import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
-import org.apache.commons.lang3.StringUtils;
+import lombok.SneakyThrows;
import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
import org.apache.shardingsphere.test.it.rewriter.engine.type.SQLExecuteType;
import
org.apache.shardingsphere.test.it.rewriter.entity.RewriteAssertionEntity;
import
org.apache.shardingsphere.test.it.rewriter.entity.RewriteAssertionsRootEntity;
@@ -30,6 +31,16 @@ import
org.apache.shardingsphere.test.it.rewriter.entity.RewriteOutputEntity;
import
org.apache.shardingsphere.test.it.rewriter.loader.RewriteAssertionsRootEntityLoader;
import java.io.File;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.sql.Date;
+import java.sql.Timestamp;
+import java.time.LocalDate;
+import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -37,9 +48,12 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
@@ -48,6 +62,26 @@ import java.util.stream.Collectors;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class SQLRewriteEngineTestParametersBuilder {
+ private static final String COMMA_WITH_SPACE_SEPARATOR = ", ";
+
+ private static final String DATABASE_TYPE_PLACEHOLDER = "${databaseType}";
+
+ private static final String DATABASE_TYPE_LOWER_PLACEHOLDER =
"${databaseTypeLower}";
+
+ private static final String HEXTORAW_PARAMETER_PREFIX = "HEXTORAW:";
+
+ private static final String ESCAPED_COMMA = "{ESCAPE_COMMA}";
+
+ private static final String ESCAPED_COLON = "{ESCAPE_COLON}";
+
+ private static final Pattern IFNULL_PARAMETER_COLUMN_PATTERN =
Pattern.compile("IFNULL\\s*\\(\\s*([`\\w.]+)\\s*,\\s*\\?",
Pattern.CASE_INSENSITIVE);
+
+ private static final Pattern DATE_PARAMETER_PATTERN =
Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
+
+ private static final Pattern DATETIME_PARAMETER_PATTERN =
Pattern.compile("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}(\\.\\d{1,6})?");
+
+ private static final Pattern TIME_PARAMETER_PATTERN =
Pattern.compile("\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,6})?)?");
+
/**
* Load test parameters.
*
@@ -97,16 +131,27 @@ public final class SQLRewriteEngineTestParametersBuilder {
private static Collection<SQLRewriteEngineTestParameters>
createTestParameters(final String type, final String fileName, final
RewriteAssertionsRootEntity rootAssertions) {
Collection<SQLRewriteEngineTestParameters> result = new LinkedList<>();
for (RewriteAssertionEntity each : rootAssertions.getAssertions()) {
+ String ruleFile = Strings.isNullOrEmpty(each.getYamlRule()) ?
rootAssertions.getYamlRule() : each.getYamlRule();
for (String databaseType :
getDatabaseTypes(each.getDatabaseTypes())) {
- // TODO support appendLiteralCases for exits cases and remove
duplicate cases
+ String actualRuleFile = resolveRuleFile(ruleFile,
databaseType);
+ boolean success = setSQLIfSQLFileNotNull(each);
+ if (success) {
+ appendLiteralCases(type, fileName, actualRuleFile, each,
databaseType, result);
+ continue;
+ }
SQLExecuteType sqlExecuteType = null ==
each.getInput().getParameters() || each.getInput().getParameters().isEmpty() ?
SQLExecuteType.LITERAL : SQLExecuteType.PLACEHOLDER;
- result.add(new SQLRewriteEngineTestParameters(type,
each.getId(), fileName, rootAssertions.getYamlRule(), each.getInput().getSql(),
- createParameters(each.getInput().getParameters()),
createOutputSQLs(each.getOutputs()),
createOutputGroupedParameters(each.getOutputs()), databaseType,
sqlExecuteType));
+ result.add(new SQLRewriteEngineTestParameters(type,
each.getId(), fileName, actualRuleFile, each.getInput().getSql(),
+ createParameters(each.getInput().getParameters(),
each.getInput().getSql()), createOutputSQLs(each.getOutputs()),
createOutputGroupedParameters(each.getOutputs()),
+ databaseType, sqlExecuteType));
}
}
return result;
}
+ private static String resolveRuleFile(final String ruleFile, final String
databaseType) {
+ return ruleFile.replace(DATABASE_TYPE_PLACEHOLDER,
databaseType).replace(DATABASE_TYPE_LOWER_PLACEHOLDER,
databaseType.toLowerCase(Locale.ENGLISH));
+ }
+
private static Collection<String> getDatabaseTypes(final String
databaseTypes) {
return Strings.isNullOrEmpty(databaseTypes) ? getAllDatabaseTypes() :
Splitter.on(',').trimResults().splitToList(databaseTypes);
}
@@ -115,18 +160,121 @@ public final class SQLRewriteEngineTestParametersBuilder
{
return Arrays.asList("MySQL", "PostgreSQL", "Oracle", "SQLServer",
"SQL92", "openGauss");
}
- private static List<Object> createParameters(final String inputParams) {
+ private static boolean setSQLIfSQLFileNotNull(final RewriteAssertionEntity
entity) {
+ boolean result = false;
+ if (null != entity.getInput().getSqlFile()) {
+ URL resource =
SQLRewriteEngineTestParametersBuilder.class.getClassLoader().getResource(entity.getInput().getSqlFile());
+ ShardingSpherePreconditions.checkNotNull(resource, () -> new
IllegalArgumentException(String.format("Resource '%s' is not found.",
entity.getInput().getSqlFile())));
+ entity.getInput().setSql(getSQLFromFilePath(resource));
+ result = true;
+ }
+ for (RewriteOutputEntity each : entity.getOutputs()) {
+ if (null != each.getSqlFile()) {
+
each.setSql(getSQLFromFilePath(Objects.requireNonNull(SQLRewriteEngineTestParametersBuilder.class.getClassLoader().getResource(each.getSqlFile()))));
+ }
+ }
+ return result;
+ }
+
+ private static void appendLiteralCases(final String type, final String
fileName, final String ruleFile, final RewriteAssertionEntity assertionEntity,
+ final String databaseType, final
Collection<SQLRewriteEngineTestParameters> result) {
+ if (null == assertionEntity.getInput().getParameters() ||
assertionEntity.getInput().getParameters().isEmpty()) {
+ result.add(new SQLRewriteEngineTestParameters(type,
assertionEntity.getId(), fileName, ruleFile,
assertionEntity.getInput().getSql(),
+
createParameters(assertionEntity.getInput().getParameters(), null),
createOutputSQLs(assertionEntity.getOutputs()),
createOutputGroupedParameters(assertionEntity.getOutputs()),
+ databaseType, SQLExecuteType.LITERAL));
+ } else {
+ result.add(new SQLRewriteEngineTestParameters(type,
assertionEntity.getId(), fileName, ruleFile,
assertionEntity.getInput().getSql(),
+
createParameters(assertionEntity.getInput().getParameters(),
assertionEntity.getInput().getSql()),
createOutputSQLs(assertionEntity.getOutputs()),
+
createOutputGroupedParameters(assertionEntity.getOutputs()), databaseType,
SQLExecuteType.PLACEHOLDER));
+ result.add(new SQLRewriteEngineTestParameters(type,
assertionEntity.getId(), fileName, ruleFile,
+ getLiteralSQL(assertionEntity.getInput().getSql(),
assertionEntity.getInput().getParameters()), Collections.emptyList(),
createLiteralOutputSQLs(assertionEntity.getOutputs()),
+
createLiteralOutputGroupedParameters(assertionEntity.getOutputs()),
databaseType, SQLExecuteType.LITERAL));
+ }
+ }
+
+ private static String getLiteralSQL(final String sql, final String
parameters) {
+ List<String> params =
+
Splitter.on(COMMA_WITH_SPACE_SEPARATOR).omitEmptyStrings().trimResults().splitToList(parameters).stream().map(SQLRewriteEngineTestParametersBuilder::createLiteralParameter)
+ .collect(Collectors.toList());
+ return params.isEmpty() ? sql : String.format(sql.replace("%",
"ÿ").replace("?", "%s"), params.toArray()).replace("ÿ", "%").replace("%%",
"%").replace("'%'", "'%%'");
+ }
+
+ private static String createLiteralParameter(final String value) {
+ String actualValue = replaceEscapedChars(value);
+ return actualValue.startsWith(HEXTORAW_PARAMETER_PREFIX) ?
String.format("HEXTORAW('%s')",
actualValue.substring(HEXTORAW_PARAMETER_PREFIX.length())) : "'" + actualValue
+ "'";
+ }
+
+ @SneakyThrows({IOException.class, URISyntaxException.class})
+ private static String getSQLFromFilePath(final URL fileURL) {
+ return
Files.readAllLines(Paths.get(fileURL.toURI())).stream().collect(Collectors.joining(System.lineSeparator()));
+ }
+
+ private static List<Object> createParameters(final String inputParams,
final String sql) {
if (null == inputParams) {
return Collections.emptyList();
}
- return
Splitter.on(",").trimResults().splitToList(inputParams).stream().map(SQLRewriteEngineTestParametersBuilder::createInputParameter).collect(Collectors.toList());
+ List<String> inputParamValues = splitParameters(inputParams);
+ List<String> ifNullColumnNames = getIfNullColumnNames(sql);
+ List<Object> result = new ArrayList<>(inputParamValues.size());
+ for (int i = 0; i < inputParamValues.size(); i++) {
+ result.add(createInputParameter(inputParamValues.get(i), i <
ifNullColumnNames.size() ? ifNullColumnNames.get(i) : null));
+ }
+ return result;
+ }
+
+ private static Object createInputParameter(final String inputParam, final
String ifNullColumnName) {
+ String actualInputParam = replaceEscapedChars(inputParam);
+ if ("NULL".equalsIgnoreCase(actualInputParam)) {
+ return null;
+ }
+ if (NumberUtils.isCreatable(actualInputParam) && null !=
ifNullColumnName) {
+ String actualColumnName = ifNullColumnName.toLowerCase();
+ if (actualColumnName.contains("_decimal") ||
actualColumnName.contains("_numeric")) {
+ return new BigDecimal(actualInputParam);
+ }
+ if (actualColumnName.contains("_float") ||
actualColumnName.contains("_real")) {
+ return Float.parseFloat(actualInputParam);
+ }
+ if (actualColumnName.contains("_double")) {
+ return Double.parseDouble(actualInputParam);
+ }
+ }
+ if (DATETIME_PARAMETER_PATTERN.matcher(actualInputParam).matches()) {
+ return Timestamp.valueOf(actualInputParam);
+ }
+ if (DATE_PARAMETER_PATTERN.matcher(actualInputParam).matches()) {
+ return Date.valueOf(LocalDate.parse(actualInputParam));
+ }
+ if (TIME_PARAMETER_PATTERN.matcher(actualInputParam).matches()) {
+ return LocalTime.parse(actualInputParam);
+ }
+ if (NumberUtils.isCreatable(actualInputParam) &&
actualInputParam.contains(".")) {
+ return new BigDecimal(actualInputParam);
+ }
+ if (NumberUtils.isCreatable(actualInputParam)) {
+ return NumberUtils.createNumber(actualInputParam);
+ }
+ return actualInputParam;
}
- private static Object createInputParameter(final String inputParam) {
- if (StringUtils.isNumeric(inputParam)) {
- return NumberUtils.createNumber(inputParam);
+ private static List<String> splitParameters(final String parameters) {
+ return null == parameters ? Collections.emptyList() :
Splitter.on(",").omitEmptyStrings().trimResults().splitToList(parameters);
+ }
+
+ private static List<String> getIfNullColumnNames(final String sql) {
+ if (null == sql) {
+ return Collections.emptyList();
}
- return "NULL".equals(inputParam) ? null : inputParam;
+ List<String> result = new LinkedList<>();
+ Matcher matcher = IFNULL_PARAMETER_COLUMN_PATTERN.matcher(sql);
+ while (matcher.find()) {
+ result.add(matcher.group(1).replace("`", ""));
+ }
+ return result;
+ }
+
+ private static String replaceEscapedChars(final String value) {
+ return value.replace(ESCAPED_COMMA, ",").replace(ESCAPED_COLON, ":");
}
private static List<String> createOutputSQLs(final
List<RewriteOutputEntity> outputs) {
@@ -137,11 +285,25 @@ public final class SQLRewriteEngineTestParametersBuilder {
return result;
}
+ private static List<String> createLiteralOutputSQLs(final
List<RewriteOutputEntity> outputs) {
+ List<String> result = new ArrayList<>(outputs.size());
+ for (RewriteOutputEntity each : outputs) {
+ result.add(getLiteralSQL(each.getSql(), each.getParameters()));
+ }
+ return result;
+ }
+
private static List<List<Object>> createOutputGroupedParameters(final
List<RewriteOutputEntity> outputs) {
List<List<Object>> result = new ArrayList<>(outputs.size());
for (RewriteOutputEntity each : outputs) {
- result.add(createParameters(each.getParameters()));
+ result.add(createParameters(each.getParameters(), null));
}
return result;
}
+
+ private static List<List<Object>>
createLiteralOutputGroupedParameters(final List<RewriteOutputEntity> outputs) {
+ List<List<Object>> result = new ArrayList<>(outputs.size());
+ outputs.forEach(each -> result.add(Collections.emptyList()));
+ return result;
+ }
}
diff --git
a/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteAssertionEntity.java
b/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteAssertionEntity.java
index 013f98fb49f..082c997aa60 100644
---
a/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteAssertionEntity.java
+++
b/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteAssertionEntity.java
@@ -37,6 +37,9 @@ public final class RewriteAssertionEntity {
@XmlAttribute(required = true)
private String id;
+ @XmlAttribute(name = "yaml-rule")
+ private String yamlRule;
+
@XmlElement(required = true)
private RewriteInputEntity input;
diff --git
a/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteInputEntity.java
b/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteInputEntity.java
index cc9a4665c06..17f561dbc19 100644
---
a/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteInputEntity.java
+++
b/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteInputEntity.java
@@ -35,6 +35,9 @@ public final class RewriteInputEntity {
@XmlAttribute(required = true)
private String sql;
+ @XmlAttribute(name = "sql-file")
+ private String sqlFile;
+
@XmlAttribute
private String parameters;
}
diff --git
a/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteOutputEntity.java
b/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteOutputEntity.java
index 015d91601a0..2b65b74e37b 100644
---
a/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteOutputEntity.java
+++
b/test/it/rewriter/src/test/java/org/apache/shardingsphere/test/it/rewriter/entity/RewriteOutputEntity.java
@@ -35,6 +35,9 @@ public final class RewriteOutputEntity {
@XmlAttribute(required = true)
private String sql;
+ @XmlAttribute(name = "sql-file")
+ private String sqlFile;
+
@XmlAttribute
private String parameters;
}
diff --git
a/test/it/rewriter/src/test/resources/META-INF/services/org.apache.shardingsphere.test.it.rewriter.engine.mocker.DialectStorageUnitMetaDataMocker
b/test/it/rewriter/src/test/resources/META-INF/services/org.apache.shardingsphere.test.it.rewriter.engine.mocker.DialectStorageUnitMetaDataMocker
new file mode 100644
index 00000000000..8dab509ae67
--- /dev/null
+++
b/test/it/rewriter/src/test/resources/META-INF/services/org.apache.shardingsphere.test.it.rewriter.engine.mocker.DialectStorageUnitMetaDataMocker
@@ -0,0 +1,18 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+org.apache.shardingsphere.test.it.rewriter.engine.mocker.type.MySQLStorageUnitMetaDataMocker