This is an automated email from the ASF dual-hosted git repository.
zhangliang 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 b3f8d287fea Add StandardSQLFederationProcessorTest (#37287)
b3f8d287fea is described below
commit b3f8d287fea0162242d7fff5242755927f63f6d5
Author: Liang Zhang <[email protected]>
AuthorDate: Sun Dec 7 02:48:12 2025 +0800
Add StandardSQLFederationProcessorTest (#37287)
* Add StandardSQLFederationProcessorTest
* Add StandardSQLFederationProcessorTest
* Add StandardSQLFederationProcessorTest
* Add StandardSQLFederationProcessorTest
---
AGENTS.md | 4 +
.../impl/StandardSQLFederationProcessorTest.java | 337 +++++++++++++++++++++
2 files changed, 341 insertions(+)
diff --git a/AGENTS.md b/AGENTS.md
index 593305aa58f..5102e2087e6 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -34,6 +34,10 @@ This guide is written **for AI coding agents only**. Follow
it literally; improv
- **Continuous Verification**: rely on automated tests and integration
validation.
- **Public-Only Tests**: unit tests must exercise behavior via public APIs
only; never use reflection to access private members.
- **Coverage Pledge**: when 100% coverage is required, enumerate every
branch/path and its planned test before coding, then implement once to reach
100% without post-hoc fixes.
+- **Mock/Spy Specification**: Use mock by default; consider spy only when the
scenario cannot be adequately represented using a mock.
+- **Strictness and Stub Control**: Enable @MockitoSettings(strictness =
Strictness.LENIENT) in the Mockito scenario or apply lenient() to specific
stubs to ensure there are no unmatched or redundant stubs; clean up any unused
stubs, imports, or local variables before committing.
+
+
## Tool Usage Guide
diff --git
a/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/engine/processor/impl/StandardSQLFederationProcessorTest.java
b/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/engine/processor/impl/StandardSQLFederationProcessorTest.java
new file mode 100644
index 00000000000..f879c78fc61
--- /dev/null
+++
b/kernel/sql-federation/core/src/test/java/org/apache/shardingsphere/sqlfederation/engine/processor/impl/StandardSQLFederationProcessorTest.java
@@ -0,0 +1,337 @@
+/*
+ * 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.sqlfederation.engine.processor.impl;
+
+import org.apache.calcite.adapter.enumerable.EnumerableConvention;
+import org.apache.calcite.adapter.enumerable.EnumerableInterpretable;
+import org.apache.calcite.adapter.enumerable.EnumerableRel;
+import org.apache.calcite.linq4j.Enumerable;
+import org.apache.calcite.linq4j.Enumerator;
+import org.apache.calcite.runtime.Bindable;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.schema.Table;
+import org.apache.calcite.schema.lookup.Lookup;
+import
org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
+import
org.apache.shardingsphere.database.connector.core.metadata.database.metadata.option.schema.DialectSchemaOption;
+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.statement.type.dml.SelectStatementContext;
+import org.apache.shardingsphere.infra.executor.sql.context.ExecutionUnit;
+import
org.apache.shardingsphere.infra.executor.sql.execute.engine.driver.jdbc.JDBCExecutionUnit;
+import
org.apache.shardingsphere.infra.executor.sql.execute.engine.driver.jdbc.JDBCExecutorCallback;
+import
org.apache.shardingsphere.infra.executor.sql.execute.result.ExecuteResult;
+import
org.apache.shardingsphere.infra.executor.sql.prepare.driver.DriverExecutionPrepareEngine;
+import org.apache.shardingsphere.infra.session.query.QueryContext;
+import
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.bound.TableSegmentBoundInfo;
+import
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.table.SimpleTableSegment;
+import
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
+import
org.apache.shardingsphere.sqlfederation.compiler.SQLFederationExecutionPlan;
+import
org.apache.shardingsphere.sqlfederation.compiler.context.CompilerContext;
+import
org.apache.shardingsphere.sqlfederation.compiler.exception.SQLFederationSchemaNotFoundException;
+import
org.apache.shardingsphere.sqlfederation.compiler.metadata.schema.SQLFederationTable;
+import
org.apache.shardingsphere.sqlfederation.compiler.rel.converter.SQLFederationRelConverter;
+import org.apache.shardingsphere.sqlfederation.context.SQLFederationContext;
+import
org.apache.shardingsphere.sqlfederation.engine.processor.SQLFederationProcessor;
+import
org.apache.shardingsphere.sqlfederation.resultset.SQLFederationResultSet;
+import
org.apache.shardingsphere.sqlfederation.resultset.converter.SQLFederationColumnTypeConverter;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+class StandardSQLFederationProcessorTest {
+
+ private final String databaseName = "foo_db";
+
+ private final String schemaName = "foo_schema";
+
+ private SQLFederationProcessor processor;
+
+ @BeforeEach
+ void setUp() {
+ processor = new StandardSQLFederationProcessor(mock(), mock());
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void assertPrepareWithNullSchema() {
+ assertDoesNotThrow(() -> processor.prepare(mock(), mock(),
databaseName, schemaName, mock(), mock(), null));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void assertPrepareAndReleaseSkipNonFederationTable() {
+ SQLFederationContext federationContext = createFederationContext(true,
null);
+ Table table = mock(Table.class);
+ SchemaPlus rootSchema = mockFlatSchemaWithTable(table);
+ DialectSchemaOption schemaOption = mock(DialectSchemaOption.class);
+
when(schemaOption.getDefaultSchema()).thenReturn(Optional.of(schemaName));
+ try (
+ MockedConstruction<DatabaseTypeRegistry> ignored =
mockConstruction(DatabaseTypeRegistry.class, (constructed, context) -> {
+ DialectDatabaseMetaData dialectMeta =
mock(DialectDatabaseMetaData.class);
+
when(dialectMeta.getSchemaOption()).thenReturn(schemaOption);
+
when(constructed.getDialectDatabaseMetaData()).thenReturn(dialectMeta);
+ })) {
+ processor.prepare(mock(), mock(), databaseName, schemaName,
federationContext, mock(), rootSchema);
+ verifyNoInteractions(table);
+ processor.release(databaseName, schemaName,
federationContext.getQueryContext(), rootSchema);
+ }
+ }
+
+ @Test
+ void assertReleaseClearImplementorForFederationTable() {
+ SQLFederationContext federationContext = createFederationContext(true,
null);
+ SQLFederationTable federationTable = mock(SQLFederationTable.class);
+ SchemaPlus rootSchema = mockSchemaTreeWithTable(databaseName,
schemaName, federationTable);
+ DialectSchemaOption schemaOption = mock(DialectSchemaOption.class);
+
when(schemaOption.getDefaultSchema()).thenReturn(Optional.of(schemaName));
+ try (
+ MockedConstruction<DatabaseTypeRegistry> ignored =
mockConstruction(DatabaseTypeRegistry.class, (constructed, context) -> {
+ DialectDatabaseMetaData dialectMeta =
mock(DialectDatabaseMetaData.class);
+
when(dialectMeta.getSchemaOption()).thenReturn(schemaOption);
+
when(constructed.getDialectDatabaseMetaData()).thenReturn(dialectMeta);
+ })) {
+ processor.release(databaseName, schemaName,
federationContext.getQueryContext(), rootSchema);
+ verify(federationTable).clearScanImplementor();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void assertPrepareWithDefaultSchemaEmpty() {
+ SQLFederationContext federationContext = createFederationContext(true,
null);
+ SchemaPlus rootSchema =
mockFlatSchemaWithTable(mock(SQLFederationTable.class));
+ DialectSchemaOption schemaOption = mock(DialectSchemaOption.class);
+ when(schemaOption.getDefaultSchema()).thenReturn(Optional.empty());
+ try (
+ MockedConstruction<DatabaseTypeRegistry> ignored =
mockConstruction(DatabaseTypeRegistry.class, (constructed, context) -> {
+ DialectDatabaseMetaData dialectMeta =
mock(DialectDatabaseMetaData.class);
+
when(dialectMeta.getSchemaOption()).thenReturn(schemaOption);
+
when(constructed.getDialectDatabaseMetaData()).thenReturn(dialectMeta);
+ })) {
+ processor.prepare(mock(), mock(), databaseName, schemaName,
federationContext, mock(CompilerContext.class), rootSchema);
+ SQLFederationTable table = (SQLFederationTable)
rootSchema.subSchemas().get(databaseName).tables().get("foo_tbl");
+ verify(table).setScanImplementor(any());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void assertPrepareThrowsWhenSchemaMissing() {
+ SQLFederationContext federationContext = createFederationContext(true,
null);
+ SchemaPlus rootSchema = mock(SchemaPlus.class);
+ when(rootSchema.subSchemas()).thenReturn(mock(Lookup.class));
+ DialectSchemaOption schemaOption = mock(DialectSchemaOption.class);
+
when(schemaOption.getDefaultSchema()).thenReturn(Optional.of(schemaName));
+ try (
+ MockedConstruction<DatabaseTypeRegistry> ignored =
mockConstruction(DatabaseTypeRegistry.class, (constructed, context) -> {
+ DialectDatabaseMetaData dialectMeta =
mock(DialectDatabaseMetaData.class);
+
when(dialectMeta.getSchemaOption()).thenReturn(schemaOption);
+
when(constructed.getDialectDatabaseMetaData()).thenReturn(dialectMeta);
+ })) {
+ assertThrows(SQLFederationSchemaNotFoundException.class, () ->
processor.prepare(mock(), mock(), databaseName, schemaName, federationContext,
mock(CompilerContext.class), rootSchema));
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void assertPrepareUsesTableBoundInfo() {
+ SQLFederationContext federationContext = createFederationContext(true,
new TableSegmentBoundInfo(new IdentifierValue("origin_db"), new
IdentifierValue("origin_schema")));
+ SQLFederationTable federationTable = mock(SQLFederationTable.class);
+ SchemaPlus rootSchema = mockSchemaTreeWithTable("origin_db",
"origin_schema", federationTable);
+ DialectSchemaOption schemaOption = mock(DialectSchemaOption.class);
+
when(schemaOption.getDefaultSchema()).thenReturn(Optional.of(schemaName));
+ try (
+ MockedConstruction<DatabaseTypeRegistry> ignored =
mockConstruction(DatabaseTypeRegistry.class, (constructed, context) -> {
+ DialectDatabaseMetaData dialectMeta =
mock(DialectDatabaseMetaData.class);
+
when(dialectMeta.getSchemaOption()).thenReturn(schemaOption);
+
when(constructed.getDialectDatabaseMetaData()).thenReturn(dialectMeta);
+ })) {
+ processor.prepare(mock(), mock(), "logic_db", "logic_schema",
federationContext, mock(CompilerContext.class), rootSchema);
+ verify(federationTable).setScanImplementor(any());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void assertExecutePlanWithEmptyParameters() {
+ DriverExecutionPrepareEngine<JDBCExecutionUnit, Connection>
prepareEngine = mock(DriverExecutionPrepareEngine.class);
+ JDBCExecutorCallback<? extends ExecuteResult> callback =
mock(JDBCExecutorCallback.class);
+ SQLFederationContext federationContext = createFederationContext(true,
null);
+
when(federationContext.getQueryContext().getParameters()).thenReturn(new
LinkedList<>());
+ CompilerContext compilerContext = mock(CompilerContext.class);
+ SchemaPlus rootSchema = mockSchemaTreeWithTable();
+ DialectSchemaOption schemaOption = mock(DialectSchemaOption.class);
+
when(schemaOption.getDefaultSchema()).thenReturn(Optional.of(schemaName));
+ try (
+ MockedConstruction<DatabaseTypeRegistry> ignored =
mockConstruction(DatabaseTypeRegistry.class, (constructed, context) -> {
+ DialectDatabaseMetaData dialectMeta =
mock(DialectDatabaseMetaData.class);
+
when(dialectMeta.getSchemaOption()).thenReturn(schemaOption);
+
when(constructed.getDialectDatabaseMetaData()).thenReturn(dialectMeta);
+ })) {
+ processor.prepare(prepareEngine, callback, databaseName,
schemaName, federationContext, compilerContext, rootSchema);
+ }
+ SQLFederationExecutionPlan executionPlan =
mock(SQLFederationExecutionPlan.class);
+
when(executionPlan.getPhysicalPlan()).thenReturn(mock(EnumerableRel.class));
+
when(executionPlan.getResultColumnType()).thenReturn(mock(org.apache.calcite.rel.type.RelDataType.class));
+ SQLFederationRelConverter converter =
mock(SQLFederationRelConverter.class);
+ Bindable<Object> bindable = mock(Bindable.class);
+ Enumerator<Object> enumerator = mock(Enumerator.class);
+ Enumerable<Object> enumerable = mock(Enumerable.class);
+ when(enumerable.enumerator()).thenReturn(enumerator);
+ when(bindable.bind(any())).thenReturn(enumerable);
+ try (
+ MockedStatic<EnumerableInterpretable> ignoredInterpretable =
mockStatic(EnumerableInterpretable.class);
+ MockedStatic<DatabaseTypedSPILoader> mockedSpiLoader =
mockStatic(DatabaseTypedSPILoader.class)) {
+ ignoredInterpretable.when(() ->
EnumerableInterpretable.toBindable(any(Map.class), any(), any(),
any())).thenReturn(bindable);
+ mockedSpiLoader.when(() -> DatabaseTypedSPILoader
+ .getService(eq(SQLFederationColumnTypeConverter.class),
any(DatabaseType.class))).thenReturn(mock(SQLFederationColumnTypeConverter.class));
+ ResultSet result = processor.executePlan(prepareEngine, callback,
executionPlan, converter, federationContext, rootSchema);
+ ((SQLFederationResultSet) result).close();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ void assertExecutePlanPreviewAndNonPreview() {
+ DriverExecutionPrepareEngine<JDBCExecutionUnit, Connection>
prepareEngine = mock(DriverExecutionPrepareEngine.class);
+ JDBCExecutorCallback<? extends ExecuteResult> callback =
mock(JDBCExecutorCallback.class);
+ SQLFederationContext federationContext =
createFederationContext(false, null);
+ Collection<ExecutionUnit> previewExecutionUnits =
federationContext.getPreviewExecutionUnits();
+ previewExecutionUnits.clear();
+ CompilerContext compilerContext = mock(CompilerContext.class);
+ SchemaPlus rootSchema = mockSchemaTreeWithTable();
+ DialectSchemaOption schemaOption = mock(DialectSchemaOption.class);
+
when(schemaOption.getDefaultSchema()).thenReturn(Optional.of(schemaName));
+ try (
+ MockedConstruction<DatabaseTypeRegistry> ignored =
mockConstruction(DatabaseTypeRegistry.class, (constructed, context) -> {
+ DialectDatabaseMetaData dialectMeta =
mock(DialectDatabaseMetaData.class);
+
when(dialectMeta.getSchemaOption()).thenReturn(schemaOption);
+
when(constructed.getDialectDatabaseMetaData()).thenReturn(dialectMeta);
+ })) {
+ processor.prepare(prepareEngine, callback, databaseName,
schemaName, federationContext, compilerContext, rootSchema);
+ }
+ SQLFederationExecutionPlan executionPlan =
mock(SQLFederationExecutionPlan.class);
+
when(executionPlan.getPhysicalPlan()).thenReturn(mock(EnumerableRel.class));
+
when(executionPlan.getResultColumnType()).thenReturn(mock(org.apache.calcite.rel.type.RelDataType.class));
+ SQLFederationRelConverter converter =
mock(SQLFederationRelConverter.class);
+ Bindable<Object> bindable = mock(Bindable.class);
+ Enumerator<Object> enumerator = mock(Enumerator.class);
+ Enumerable<Object> enumerable = mock(Enumerable.class);
+ when(enumerable.enumerator()).thenReturn(enumerator);
+ when(bindable.bind(any())).thenReturn(enumerable);
+ try (
+ MockedStatic<EnumerableInterpretable> ignoredInterpretable =
mockStatic(EnumerableInterpretable.class);
+ MockedStatic<DatabaseTypedSPILoader> mockedSpiLoader =
mockStatic(DatabaseTypedSPILoader.class)) {
+ ignoredInterpretable.when(() ->
EnumerableInterpretable.toBindable(any(Map.class), any(), any(),
any())).thenReturn(bindable);
+ mockedSpiLoader.when(() -> DatabaseTypedSPILoader
+ .getService(eq(SQLFederationColumnTypeConverter.class),
any(DatabaseType.class))).thenReturn(mock(SQLFederationColumnTypeConverter.class));
+ ResultSet previewResult = processor.executePlan(prepareEngine,
callback, executionPlan, converter, federationContext, rootSchema);
+ ((SQLFederationResultSet) previewResult).close();
+ ResultSet normalResult = processor.executePlan(prepareEngine,
callback, executionPlan, converter, federationContext, rootSchema);
+ ((SQLFederationResultSet) normalResult).close();
+ }
+ }
+
+ @Test
+ void assertGetConvention() {
+ assertThat(processor.getConvention(),
is(EnumerableConvention.INSTANCE));
+ }
+
+ private SQLFederationContext createFederationContext(final boolean
preview, final TableSegmentBoundInfo tableSegmentBoundInfo) {
+ SelectStatementContext sqlStatementContext =
mock(SelectStatementContext.class, RETURNS_DEEP_STUBS);
+
when(sqlStatementContext.getTablesContext().getSchemaNames()).thenReturn(Collections.singleton("pg_catalog"));
+ SimpleTableSegment tableSegment = mock(SimpleTableSegment.class,
RETURNS_DEEP_STUBS);
+ if (null == tableSegmentBoundInfo) {
+
when(tableSegment.getTableName().getTableBoundInfo()).thenReturn(Optional.empty());
+ } else {
+
when(tableSegment.getTableName().getTableBoundInfo()).thenReturn(Optional.of(tableSegmentBoundInfo));
+ }
+
when(sqlStatementContext.getTablesContext().getSimpleTables()).thenReturn(Collections.singleton(tableSegment));
+ QueryContext queryContext = mock(QueryContext.class);
+
when(queryContext.getSqlStatementContext()).thenReturn(sqlStatementContext);
+ when(queryContext.getSql()).thenReturn("SELECT 1");
+ when(queryContext.getParameters()).thenReturn(new
ArrayList<>(Collections.singletonList(1)));
+ return new SQLFederationContext(preview, queryContext, mock(), "pid");
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private SchemaPlus mockFlatSchemaWithTable(final Table table) {
+ Lookup tableLookup = mock(Lookup.class);
+ when(tableLookup.get(any())).thenReturn(table);
+ SchemaPlus databaseSchema = mock(SchemaPlus.class, RETURNS_DEEP_STUBS);
+ when(databaseSchema.tables()).thenReturn(tableLookup);
+ Lookup rootSchemas = mock(Lookup.class);
+ when(rootSchemas.get(databaseName)).thenReturn(databaseSchema);
+ SchemaPlus result = mock(SchemaPlus.class);
+ when(result.subSchemas()).thenReturn(rootSchemas);
+ return result;
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private SchemaPlus mockSchemaTreeWithTable(final String databaseName,
final String schemaName, final Table table) {
+ SchemaPlus logicSchema = mock(SchemaPlus.class, RETURNS_DEEP_STUBS);
+ when(logicSchema.tables().get(any())).thenReturn(table);
+ Lookup schemaLookup = mock(Lookup.class);
+ when(schemaLookup.get(schemaName)).thenReturn(logicSchema);
+ SchemaPlus databaseSchema = mock(SchemaPlus.class);
+ when(databaseSchema.subSchemas()).thenReturn(schemaLookup);
+ Lookup rootSchemas = mock(Lookup.class);
+ when(rootSchemas.get(databaseName)).thenReturn(databaseSchema);
+ SchemaPlus result = mock(SchemaPlus.class);
+ when(result.subSchemas()).thenReturn(rootSchemas);
+ return result;
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ private SchemaPlus mockSchemaTreeWithTable() {
+ Lookup schemaLookup = mock(Lookup.class);
+ when(schemaLookup.get(schemaName)).thenReturn(mock(SchemaPlus.class,
RETURNS_DEEP_STUBS));
+ SchemaPlus databaseSchema = mock(SchemaPlus.class);
+ when(databaseSchema.subSchemas()).thenReturn(schemaLookup);
+ Lookup rootSchemas = mock(Lookup.class);
+ when(rootSchemas.get(databaseName)).thenReturn(databaseSchema);
+ SchemaPlus result = mock(SchemaPlus.class);
+ when(result.subSchemas()).thenReturn(rootSchemas);
+ return result;
+ }
+}