This is an automated email from the ASF dual-hosted git repository. ppa pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push: new a9d4ae29e6 IGNITE-21166 Sql. Provide internal API to get a columns metadata for non-executed query (#3037) a9d4ae29e6 is described below commit a9d4ae29e64c1282a5af7afc04c1f3d3325d4989 Author: Pavel Pereslegin <xxt...@gmail.com> AuthorDate: Tue Jan 16 13:30:35 2024 +0300 IGNITE-21166 Sql. Provide internal API to get a columns metadata for non-executed query (#3037) --- .../sql/ClientSqlParameterMetadataRequest.java | 4 +- .../client/fakes/FakeIgniteQueryProcessor.java | 4 +- .../sql/engine/ItDynamicParameterTest.java | 23 +++++---- .../ignite/internal/sql/engine/QueryProcessor.java | 9 ++-- .../internal/sql/engine/SqlQueryProcessor.java | 12 ++--- .../internal/sql/engine/prepare/QueryMetadata.java | 53 ++++++++++++++++++++ .../internal/sql/engine/util/QueryCheckerTest.java | 56 ++++++++++++++++++---- .../internal/sql/engine/util/QueryCheckerImpl.java | 42 +++++++++++----- 8 files changed, 156 insertions(+), 47 deletions(-) diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlParameterMetadataRequest.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlParameterMetadataRequest.java index d4eef1a73f..4eb61cfeca 100644 --- a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlParameterMetadataRequest.java +++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlParameterMetadataRequest.java @@ -25,7 +25,7 @@ import org.apache.ignite.internal.client.proto.ClientMessagePacker; import org.apache.ignite.internal.client.proto.ClientMessageUnpacker; import org.apache.ignite.internal.sql.engine.QueryProcessor; import org.apache.ignite.internal.sql.engine.QueryProperty; -import org.apache.ignite.internal.sql.engine.prepare.ParameterMetadata; +import org.apache.ignite.internal.sql.engine.prepare.QueryMetadata; import org.apache.ignite.internal.sql.engine.property.SqlProperties; import org.apache.ignite.internal.sql.engine.property.SqlPropertiesHelper; @@ -57,7 +57,7 @@ public class ClientSqlParameterMetadataRequest { return processor.prepareSingleAsync(properties, tx, query).thenAccept(meta -> writeMeta(out, meta)); } - private static void writeMeta(ClientMessagePacker out, ParameterMetadata meta) { + private static void writeMeta(ClientMessagePacker out, QueryMetadata meta) { var types = meta.parameterTypes(); out.packInt(types.size()); diff --git a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgniteQueryProcessor.java b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgniteQueryProcessor.java index 528ddabd3e..fbdfc5ffb2 100644 --- a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgniteQueryProcessor.java +++ b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeIgniteQueryProcessor.java @@ -23,7 +23,7 @@ import java.util.concurrent.CompletableFuture; import org.apache.ignite.internal.sql.engine.AsyncSqlCursor; import org.apache.ignite.internal.sql.engine.InternalSqlRow; import org.apache.ignite.internal.sql.engine.QueryProcessor; -import org.apache.ignite.internal.sql.engine.prepare.ParameterMetadata; +import org.apache.ignite.internal.sql.engine.prepare.QueryMetadata; import org.apache.ignite.internal.sql.engine.property.SqlProperties; import org.apache.ignite.internal.tx.InternalTransaction; import org.apache.ignite.tx.IgniteTransactions; @@ -34,7 +34,7 @@ import org.jetbrains.annotations.Nullable; */ public class FakeIgniteQueryProcessor implements QueryProcessor { @Override - public CompletableFuture<ParameterMetadata> prepareSingleAsync(SqlProperties properties, + public CompletableFuture<QueryMetadata> prepareSingleAsync(SqlProperties properties, @Nullable InternalTransaction transaction, String qry, Object... params) { throw new UnsupportedOperationException(); } diff --git a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDynamicParameterTest.java b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDynamicParameterTest.java index 22ebd676f5..ffc6803f57 100644 --- a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDynamicParameterTest.java +++ b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDynamicParameterTest.java @@ -33,7 +33,6 @@ import java.util.stream.Stream; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.sql.type.SqlTypeName; import org.apache.ignite.internal.sql.BaseSqlIntegrationTest; -import org.apache.ignite.internal.sql.engine.prepare.ParameterMetadata; import org.apache.ignite.internal.sql.engine.prepare.ParameterType; import org.apache.ignite.internal.sql.engine.property.SqlProperties; import org.apache.ignite.internal.sql.engine.property.SqlPropertiesHelper; @@ -294,9 +293,9 @@ public class ItDynamicParameterTest extends BaseSqlIntegrationTest { @ParameterizedTest @MethodSource("statementsWithParameters") public void testGetParameterTypesSimple(String stmt, List<ColumnType> expectedTypes, Object[] params) { - ParameterMetadata parameterTypes = getParameterMetadata(stmt, params); + List<ParameterType> parameterTypes = getParameterTypes(stmt, params); - List<ColumnType> columnTypes = parameterTypes.parameterTypes() + List<ColumnType> columnTypes = parameterTypes .stream() .map(ParameterType::columnType) .collect(Collectors.toList()); @@ -321,7 +320,7 @@ public class ItDynamicParameterTest extends BaseSqlIntegrationTest { assertThrowsSqlException( Sql.STMT_VALIDATION_ERR, "Unexpected number of query parameters", - () -> getParameterMetadata("SELECT ? + ?", 1, 2, 3)); + () -> getParameterTypes("SELECT ? + ?", 1, 2, 3)); } @Test @@ -341,9 +340,9 @@ public class ItDynamicParameterTest extends BaseSqlIntegrationTest { log.info("SELECT from column names: {}", stmt); - ParameterMetadata parameterTypes = getParameterMetadata(stmt.toString()); + List<ParameterType> parameterTypes = getParameterTypes(stmt.toString()); - List<NativeType> actualTypes = parameterTypes.parameterTypes().stream() + List<NativeType> actualTypes = parameterTypes.stream() .map(p -> TypeUtils.columnType2NativeType(p.columnType(), p.precision(), p.scale(), p.precision())) .collect(Collectors.toList()); @@ -362,9 +361,9 @@ public class ItDynamicParameterTest extends BaseSqlIntegrationTest { log.info("INSERT from column names: {}", stmt); - ParameterMetadata parameterTypes = getParameterMetadata(stmt.toString()); + List<ParameterType> parameterTypes = getParameterTypes(stmt.toString()); - List<NativeType> actualTypes = parameterTypes.parameterTypes().stream() + List<NativeType> actualTypes = parameterTypes.stream() .map(p -> TypeUtils.columnType2NativeType(p.columnType(), p.precision(), p.scale(), p.precision())) .collect(Collectors.toList()); @@ -389,9 +388,9 @@ public class ItDynamicParameterTest extends BaseSqlIntegrationTest { log.info("UPDATE from column names: {}", stmt); - ParameterMetadata parameterTypes = getParameterMetadata(stmt.toString()); + List<ParameterType> parameterTypes = getParameterTypes(stmt.toString()); - List<NativeType> actualTypes = parameterTypes.parameterTypes().stream() + List<NativeType> actualTypes = parameterTypes.stream() .map(p -> TypeUtils.columnType2NativeType(p.columnType(), p.precision(), p.scale(), p.precision())) .collect(Collectors.toList()); @@ -452,9 +451,9 @@ public class ItDynamicParameterTest extends BaseSqlIntegrationTest { () -> assertQuery(query).withParams(params).check()); } - private ParameterMetadata getParameterMetadata(String query, Object... params) { + private List<ParameterType> getParameterTypes(String query, Object... params) { QueryProcessor qryProc = queryProcessor(); SqlProperties properties = SqlPropertiesHelper.emptyProperties(); - return await(qryProc.prepareSingleAsync(properties, null, query, params)); + return await(qryProc.prepareSingleAsync(properties, null, query, params)).parameterTypes(); } } diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java index 258651d37e..190b3a6a58 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/QueryProcessor.java @@ -19,7 +19,7 @@ package org.apache.ignite.internal.sql.engine; import java.util.concurrent.CompletableFuture; import org.apache.ignite.internal.manager.IgniteComponent; -import org.apache.ignite.internal.sql.engine.prepare.ParameterMetadata; +import org.apache.ignite.internal.sql.engine.prepare.QueryMetadata; import org.apache.ignite.internal.sql.engine.property.SqlProperties; import org.apache.ignite.internal.tx.InternalTransaction; import org.apache.ignite.lang.IgniteException; @@ -32,18 +32,19 @@ import org.jetbrains.annotations.Nullable; public interface QueryProcessor extends IgniteComponent { /** - * Returns parameter metadata for the given statement. This method uses optional array of parameters to assist with type inference. + * Returns columns and parameters metadata for the given statement. + * This method uses optional array of parameters to assist with type inference. * * @param properties User query properties. See {@link QueryProperty} for available properties. * @param transaction A transaction to use to resolve a schema. * @param qry Single statement SQL query. * @param params Query parameters. - * @return Parameter metadata. + * @return Query metadata. * * @throws IgniteException in case of an error. * @see QueryProperty */ - CompletableFuture<ParameterMetadata> prepareSingleAsync(SqlProperties properties, + CompletableFuture<QueryMetadata> prepareSingleAsync(SqlProperties properties, @Nullable InternalTransaction transaction, String qry, Object... params); diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java index c660152014..cfc62f603c 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java @@ -91,9 +91,9 @@ import org.apache.ignite.internal.sql.engine.exec.mapping.ExecutionTargetFactory import org.apache.ignite.internal.sql.engine.exec.mapping.ExecutionTargetProvider; import org.apache.ignite.internal.sql.engine.exec.mapping.MappingServiceImpl; import org.apache.ignite.internal.sql.engine.message.MessageServiceImpl; -import org.apache.ignite.internal.sql.engine.prepare.ParameterMetadata; import org.apache.ignite.internal.sql.engine.prepare.PrepareService; import org.apache.ignite.internal.sql.engine.prepare.PrepareServiceImpl; +import org.apache.ignite.internal.sql.engine.prepare.QueryMetadata; import org.apache.ignite.internal.sql.engine.prepare.QueryPlan; import org.apache.ignite.internal.sql.engine.property.SqlProperties; import org.apache.ignite.internal.sql.engine.property.SqlPropertiesHelper; @@ -428,7 +428,7 @@ public class SqlQueryProcessor implements QueryProcessor { /** {@inheritDoc} */ @Override - public CompletableFuture<ParameterMetadata> prepareSingleAsync(SqlProperties properties, + public CompletableFuture<QueryMetadata> prepareSingleAsync(SqlProperties properties, @Nullable InternalTransaction transaction, String qry, Object... params) { @@ -489,7 +489,7 @@ public class SqlQueryProcessor implements QueryProcessor { return service; } - private CompletableFuture<ParameterMetadata> prepareSingleAsync0( + private CompletableFuture<QueryMetadata> prepareSingleAsync0( SqlProperties properties, @Nullable InternalTransaction explicitTransaction, String sql, @@ -500,9 +500,9 @@ public class SqlQueryProcessor implements QueryProcessor { QueryCancel queryCancel = new QueryCancel(); - CompletableFuture<ParameterMetadata> start = new CompletableFuture<>(); + CompletableFuture<QueryMetadata> start = new CompletableFuture<>(); - CompletableFuture<ParameterMetadata> stage = start.thenCompose(ignored -> { + CompletableFuture<QueryMetadata> stage = start.thenCompose(ignored -> { ParsedResult result = parserService.parse(sql); validateParsedStatement(properties0, result); @@ -511,7 +511,7 @@ public class SqlQueryProcessor implements QueryProcessor { HybridTimestamp timestamp = explicitTransaction != null ? explicitTransaction.startTimestamp() : clock.now(); return prepareParsedStatement(schemaName, result, timestamp, queryCancel, params) - .thenApply(QueryPlan::parameterMetadata); + .thenApply(plan -> new QueryMetadata(plan.metadata(), plan.parameterMetadata())); }); // TODO IGNITE-20078 Improve (or remove) CancellationException handling. diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryMetadata.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryMetadata.java new file mode 100644 index 0000000000..dedf6fb423 --- /dev/null +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/QueryMetadata.java @@ -0,0 +1,53 @@ +/* + * 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.ignite.internal.sql.engine.prepare; + +import java.util.List; +import org.apache.ignite.internal.tostring.IgniteToStringInclude; +import org.apache.ignite.internal.tostring.S; +import org.apache.ignite.sql.ColumnMetadata; +import org.apache.ignite.sql.ResultSetMetadata; + +/** + * Query metadata combines {@link ParameterMetadata dynamic parameters metadata} and {@link ColumnMetadata columns metadata}. + */ +public class QueryMetadata { + @IgniteToStringInclude + private final List<ColumnMetadata> columns; + + @IgniteToStringInclude + private final List<ParameterType> parameterTypes; + + public QueryMetadata(ResultSetMetadata resultSetMetadata, ParameterMetadata parameterMetadata) { + this.columns = resultSetMetadata.columns(); + this.parameterTypes = parameterMetadata.parameterTypes(); + } + + public List<ColumnMetadata> columns() { + return columns; + } + + public List<ParameterType> parameterTypes() { + return parameterTypes; + } + + @Override + public String toString() { + return S.toString(QueryMetadata.class, this); + } +} diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerTest.java index caed3ae0bd..a513697824 100644 --- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerTest.java +++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerTest.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal.sql.engine.util; +import static org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrows; import static org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause; import static org.apache.ignite.internal.util.CompletableFutures.nullCompletedFuture; import static org.hamcrest.Matchers.containsString; @@ -33,7 +34,7 @@ import org.apache.ignite.internal.sql.engine.framework.NoOpTransaction; import org.apache.ignite.internal.sql.engine.framework.TestBuilders; import org.apache.ignite.internal.sql.engine.framework.TestCluster; import org.apache.ignite.internal.sql.engine.framework.TestNode; -import org.apache.ignite.internal.sql.engine.prepare.ParameterMetadata; +import org.apache.ignite.internal.sql.engine.prepare.QueryMetadata; import org.apache.ignite.internal.sql.engine.prepare.QueryPlan; import org.apache.ignite.internal.sql.engine.property.SqlProperties; import org.apache.ignite.internal.sql.engine.tx.QueryTransactionWrapperImpl; @@ -216,15 +217,15 @@ public class QueryCheckerTest extends BaseIgniteAbstractTest { @Test void testMetadata() { - assertQuery("SELECT * FROM t1") + assertQueryMeta("SELECT * FROM t1") .columnNames("ID", "VAL") .check(); - assertQuery("SELECT * FROM t1") + assertQueryMeta("SELECT * FROM t1") .columnTypes(Integer.class, Integer.class) .check(); - assertQuery("SELECT id, val::DECIMAL(19, 2) as val_dec, id::VARCHAR(64) as id_str FROM t1") + assertQueryMeta("SELECT id, val::DECIMAL(19, 2) as val_dec, id::VARCHAR(64) as id_str FROM t1") .columnMetadata( new MetadataMatcher() .name("ID") @@ -248,13 +249,44 @@ public class QueryCheckerTest extends BaseIgniteAbstractTest { .nullable(false) ) .check(); + + // Test that validates the results cannot be executed correctly without actually executing the query. + assertThrows( + AssertionError.class, + () -> assertQueryMeta("SELECT * FROM t1") + .columnTypes(Integer.class, Integer.class) + .returns(1, 1) + .returns(2, 2) + .check(), + "Expected that the query will only be prepared, but not executed" + ); + + // Test that only checks metadata should not execute the query. + assertThrows( + AssertionError.class, + () -> assertQuery("SELECT * FROM t1") + .columnTypes(Integer.class, Integer.class) + .check(), + "Expected that the query will be executed" + ); } private static QueryChecker assertQuery(String qry) { TestNode testNode = CLUSTER.node(NODE_NAME); return queryCheckerFactory.create( - new TestQueryProcessor(testNode), + new TestQueryProcessor(testNode, false), + new TestIgniteTransactions(), + null, + qry + ); + } + + private static QueryChecker assertQueryMeta(String qry) { + TestNode testNode = CLUSTER.node(NODE_NAME); + + return queryCheckerFactory.create( + new TestQueryProcessor(testNode, true), new TestIgniteTransactions(), null, qry @@ -263,15 +295,22 @@ public class QueryCheckerTest extends BaseIgniteAbstractTest { private static class TestQueryProcessor implements QueryProcessor { private final TestNode node; + private final boolean prepareOnly; - TestQueryProcessor(TestNode node) { + TestQueryProcessor(TestNode node, boolean prepareOnly) { this.node = node; + this.prepareOnly = prepareOnly; } @Override - public CompletableFuture<ParameterMetadata> prepareSingleAsync(SqlProperties properties, + public CompletableFuture<QueryMetadata> prepareSingleAsync(SqlProperties properties, @Nullable InternalTransaction transaction, String qry, Object... params) { - throw new UnsupportedOperationException(); + assert params == null || params.length == 0 : "params are not supported"; + assert prepareOnly : "Expected that the query will be executed"; + + QueryPlan plan = node.prepare(qry); + + return CompletableFuture.completedFuture(new QueryMetadata(plan.metadata(), plan.parameterMetadata())); } @Override @@ -283,6 +322,7 @@ public class QueryCheckerTest extends BaseIgniteAbstractTest { Object... params ) { assert params == null || params.length == 0 : "params are not supported"; + assert !prepareOnly : "Expected that the query will only be prepared, but not executed"; QueryPlan plan = node.prepare(qry); AsyncCursor<InternalSqlRow> dataCursor = node.executePlan(plan); diff --git a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerImpl.java b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerImpl.java index 1a154cca07..5afdfae3ce 100644 --- a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerImpl.java +++ b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryCheckerImpl.java @@ -27,6 +27,7 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; import java.lang.reflect.Type; @@ -48,6 +49,7 @@ import org.apache.ignite.internal.sql.engine.QueryProcessor; import org.apache.ignite.internal.sql.engine.QueryProperty; import org.apache.ignite.internal.sql.engine.SqlQueryType; import org.apache.ignite.internal.sql.engine.hint.IgniteHint; +import org.apache.ignite.internal.sql.engine.prepare.QueryMetadata; import org.apache.ignite.internal.sql.engine.property.SqlProperties; import org.apache.ignite.internal.sql.engine.property.SqlPropertiesHelper; import org.apache.ignite.internal.tx.InternalTransaction; @@ -300,6 +302,18 @@ abstract class QueryCheckerImpl implements QueryChecker { } } } + + // Check column metadata only. + if (resultChecker == null && metadataMatchers != null) { + QueryMetadata queryMetadata = await(qryProc.prepareSingleAsync(PROPERTIES, tx, qry, params)); + + assertNotNull(queryMetadata); + + checkColumnsMetadata(queryMetadata.columns()); + + return; + } + // Check result. CompletableFuture<AsyncSqlCursor<InternalSqlRow>> cursors = qryProc.querySingleAsync(PROPERTIES, transactions(), tx, qry, params); @@ -309,19 +323,7 @@ abstract class QueryCheckerImpl implements QueryChecker { checkMetadata(cur.metadata()); if (metadataMatchers != null) { - List<ColumnMetadata> columnMetadata = cur.metadata().columns(); - - Iterator<ColumnMetadata> valueIterator = columnMetadata.iterator(); - Iterator<ColumnMatcher> matcherIterator = metadataMatchers.iterator(); - - while (matcherIterator.hasNext() && valueIterator.hasNext()) { - ColumnMatcher matcher = matcherIterator.next(); - ColumnMetadata actualElement = valueIterator.next(); - - matcher.check(actualElement); - } - - assertEquals(metadataMatchers.size(), columnMetadata.size(), "Column metadata doesn't match"); + checkColumnsMetadata(cur.metadata().columns()); } List<InternalSqlRow> rows = Commons.cast(getAllFromCursor(cur)); @@ -332,6 +334,20 @@ abstract class QueryCheckerImpl implements QueryChecker { } } + private void checkColumnsMetadata(List<ColumnMetadata> columnsMetadata) { + Iterator<ColumnMetadata> valueIterator = columnsMetadata.iterator(); + Iterator<ColumnMatcher> matcherIterator = metadataMatchers.iterator(); + + while (matcherIterator.hasNext() && valueIterator.hasNext()) { + ColumnMatcher matcher = matcherIterator.next(); + ColumnMetadata actualElement = valueIterator.next(); + + matcher.check(actualElement); + } + + assertEquals(metadataMatchers.size(), columnsMetadata.size(), "Column metadata doesn't match"); + } + @Override public String toString() { return QueryCheckerImpl.class.getSimpleName() + "[sql=" + queryTemplate.originalQueryString() + "]";