diff --git a/.github/workflows/prestocpp-linux-build-and-unit-test.yml b/.github/workflows/prestocpp-linux-build-and-unit-test.yml index dee1b269d0a0e..262e89d2d6e9e 100644 --- a/.github/workflows/prestocpp-linux-build-and-unit-test.yml +++ b/.github/workflows/prestocpp-linux-build-and-unit-test.yml @@ -165,6 +165,77 @@ jobs: -Duser.timezone=America/Bahia_Banderas \ -T1C + prestocpp-linux-presto-native-tests: + needs: prestocpp-linux-build-for-test + runs-on: ubuntu-22.04 + container: + image: prestodb/presto-native-dependency:0.292-20250204112033-cf8ba84 + env: + MAVEN_OPTS: "-Xmx4G -XX:+ExitOnOutOfMemoryError" + MAVEN_FAST_INSTALL: "-B -V --quiet -T 1C -DskipTests -Dair.check.skip-all -Dmaven.javadoc.skip=true" + MAVEN_TEST: "-B -Dair.check.skip-all -Dmaven.javadoc.skip=true -DLogTestDurationListener.enabled=true --fail-at-end" + steps: + - uses: actions/checkout@v4 + + - name: Fix git permissions + # Usually actions/checkout does this but as we run in a container + # it doesn't work + run: git config --global --add safe.directory ${GITHUB_WORKSPACE} + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: presto-native-build + path: presto-native-execution/_build/release + + # Permissions are lost when uploading. Details here: https://github.com/actions/upload-artifact/issues/38 + - name: Restore execute permissions and library path + run: | + chmod +x ${GITHUB_WORKSPACE}/presto-native-execution/_build/release/presto_cpp/main/presto_server + chmod +x ${GITHUB_WORKSPACE}/presto-native-execution/_build/release/velox/velox/functions/remote/server/velox_functions_remote_server_main + # Ensure transitive dependency libboost-iostreams is found. + ldconfig /usr/local/lib + + - name: Install OpenJDK8 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '8' + cache: 'maven' + - name: Download nodejs to maven cache + run: .github/bin/download_nodejs + + - name: Maven install + env: + # Use different Maven options to install. + MAVEN_OPTS: "-Xmx2G -XX:+ExitOnOutOfMemoryError" + run: | + for i in $(seq 1 3); do ./mvnw clean install $MAVEN_FAST_INSTALL -pl 'presto-native-tests' -am && s=0 && break || s=$? && sleep 10; done; (exit $s) + + - name: Run presto-native tests + run: | + export PRESTO_SERVER_PATH="${GITHUB_WORKSPACE}/presto-native-execution/_build/release/presto_cpp/main/presto_server" + export TESTFILES=`find ./presto-native-tests/src/test -type f -name 'Test*.java'` + # Convert file paths to comma separated class names + export TESTCLASSES= + for test_file in $TESTFILES + do + tmp=${test_file##*/} + test_class=${tmp%%\.*} + export TESTCLASSES="${TESTCLASSES},$test_class" + done + export TESTCLASSES=${TESTCLASSES#,} + echo "TESTCLASSES = $TESTCLASSES" + + mvn test \ + ${MAVEN_TEST} \ + -pl 'presto-native-tests' \ + -Dtest="${TESTCLASSES}" \ + -DPRESTO_SERVER=${PRESTO_SERVER_PATH} \ + -DDATA_DIR=${RUNNER_TEMP} \ + -Duser.timezone=America/Bahia_Banderas \ + -T1C + prestocpp-linux-presto-sidecar-tests: needs: prestocpp-linux-build-for-test runs-on: ubuntu-22.04 diff --git a/.github/workflows/test-other-modules.yml b/.github/workflows/test-other-modules.yml index afb84cfd7bf5d..fd6496390499c 100644 --- a/.github/workflows/test-other-modules.yml +++ b/.github/workflows/test-other-modules.yml @@ -68,6 +68,7 @@ jobs: run: | ./mvnw test -T 1 ${MAVEN_TEST} -pl ' !presto-tests, + !presto-native-tests, !presto-accumulo, !presto-cassandra, !presto-hive, diff --git a/pom.xml b/pom.xml index babee5c56cb0a..a7ac6a1613f94 100644 --- a/pom.xml +++ b/pom.xml @@ -203,6 +203,7 @@ presto-test-coverage presto-hudi presto-native-execution + presto-native-tests presto-router presto-open-telemetry redis-hbo-provider diff --git a/presto-native-execution/src/test/java/com/facebook/presto/nativeworker/PrestoNativeQueryRunnerUtils.java b/presto-native-execution/src/test/java/com/facebook/presto/nativeworker/PrestoNativeQueryRunnerUtils.java index 9d6569ad8bcb3..8e72dc28e4f3c 100644 --- a/presto-native-execution/src/test/java/com/facebook/presto/nativeworker/PrestoNativeQueryRunnerUtils.java +++ b/presto-native-execution/src/test/java/com/facebook/presto/nativeworker/PrestoNativeQueryRunnerUtils.java @@ -44,7 +44,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; import java.util.UUID; @@ -128,7 +130,7 @@ public static QueryRunner createQueryRunner( defaultQueryRunner.close(); return createNativeQueryRunner(dataDirectory.get().toString(), prestoServerPath.get(), workerCount, cacheMaxSize, true, Optional.empty(), - storageFormat, addStorageFormatToPath, false, isCoordinatorSidecarEnabled, false, enableRuntimeMetricsCollection, enableSsdCache); + storageFormat, addStorageFormatToPath, false, isCoordinatorSidecarEnabled, false, enableRuntimeMetricsCollection, enableSsdCache, Collections.emptyMap()); } public static QueryRunner createJavaQueryRunner() @@ -335,7 +337,8 @@ public static QueryRunner createNativeQueryRunner( boolean isCoordinatorSidecarEnabled, boolean singleNodeExecutionEnabled, boolean enableRuntimeMetricsCollection, - boolean enableSsdCache) + boolean enableSsdCache, + Map extraProperties) throws Exception { // The property "hive.allow-drop-table" needs to be set to true because security is always "legacy" in NativeQueryRunner. @@ -359,6 +362,7 @@ public static QueryRunner createNativeQueryRunner( .put("experimental.internal-communication.thrift-transport-enabled", String.valueOf(useThrift)) .putAll(getNativeWorkerSystemProperties()) .putAll(isCoordinatorSidecarEnabled ? getNativeSidecarProperties() : ImmutableMap.of()) + .putAll(extraProperties) .build(), coordinatorProperties.build(), "legacy", @@ -420,6 +424,28 @@ public static QueryRunner createNativeQueryRunner(String remoteFunctionServerUds return createNativeQueryRunner(false, DEFAULT_STORAGE_FORMAT, Optional.ofNullable(remoteFunctionServerUds), false, false, false, false, false); } + public static QueryRunner createNativeQueryRunner(Map extraProperties, String storageFormat) + throws Exception + { + int cacheMaxSize = 0; + NativeQueryRunnerParameters nativeQueryRunnerParameters = getNativeQueryRunnerParameters(); + return createNativeQueryRunner( + nativeQueryRunnerParameters.dataDirectory.toString(), + nativeQueryRunnerParameters.serverBinary.toString(), + nativeQueryRunnerParameters.workerCount, + cacheMaxSize, + true, + Optional.empty(), + storageFormat, + true, + false, + false, + false, + false, + false, + extraProperties); + } + public static QueryRunner createNativeQueryRunner(boolean useThrift) throws Exception { @@ -464,7 +490,8 @@ public static QueryRunner createNativeQueryRunner( isCoordinatorSidecarEnabled, singleNodeExecutionEnabled, enableRuntimeMetricsCollection, - enableSSDCache); + enableSSDCache, + Collections.emptyMap()); } // Start the remote function server. Return the UDS path used to communicate with it. diff --git a/presto-native-tests/README.md b/presto-native-tests/README.md new file mode 100644 index 0000000000000..a882b2a33b463 --- /dev/null +++ b/presto-native-tests/README.md @@ -0,0 +1,31 @@ +# Presto Native Tests + +This module contains end-to-end tests that run queries from test classes in +the `presto-tests` module with Presto C++ workers. Please build the module +`presto-native-execution` first. + +The following command can be used to run all tests in this module: +``` +mvn test + -pl 'presto-native-tests' + -Dtest="com.facebook.presto.nativetests.Test*" + -Duser.timezone=America/Bahia_Banderas + -DPRESTO_SERVER=${PRESTO_HOME}/presto-native-execution/cmake-build-debug/presto_cpp/main/presto_server + -DWORKER_COUNT=${WORKER_COUNT} -T1C +``` +Please update JVM argument `PRESTO_SERVER` to point to the Presto C++ worker +binary `presto_server`. + +## Adding new tests + +Presto C++ currently does not have the same behavior as Presto for certain +queries. This could be because of missing types, missing function signatures, +among other reasons. Tests with these unsupported queries are therefore +expected to fail and the test asserts the error message is as expected. + +Issues should also be created for the failing queries, so they are documented +and fixed. Please add the tag `presto-native-tests` for these issues. +Once all the failures in a testcase are fixed, the overriden test in this +module should be removed and the testcase in the corresponding base class in +`presto-tests` would be the single source of truth for Presto SQL coverage +tests. diff --git a/presto-native-tests/pom.xml b/presto-native-tests/pom.xml new file mode 100644 index 0000000000000..d412e684a162c --- /dev/null +++ b/presto-native-tests/pom.xml @@ -0,0 +1,109 @@ + + + 4.0.0 + + + com.facebook.presto + presto-root + 0.292-SNAPSHOT + + + presto-native-tests + presto-native-tests + Presto Native Tests + + + ${project.parent.basedir} + + + + + org.testng + testng + + + + com.facebook.presto + presto-main + ${project.version} + test + + + + com.facebook.presto + presto-native-execution + ${project.version} + test-jar + test + + + + com.facebook.presto + presto-tests + ${project.version} + test + + + + com.facebook.presto + presto-tpcds + ${project.version} + test + + + + com.google.guava + guava + + + + + + + + pl.project13.maven + git-commit-id-plugin + + true + + + + org.basepom.maven + duplicate-finder-maven-plugin + + + parquet.thrift + about.html + mozilla/public-suffix-list.txt + iceberg-build.properties + org.apache.avro.data/Json.avsc + + + com.esotericsoftware.kryo.* + com.esotericsoftware.minlog.Log + com.esotericsoftware.reflectasm.* + module-info + META-INF.versions.9.module-info + org.apache.avro.* + com.github.benmanes.caffeine.* + org.roaringbitmap.* + + + + + org.apache.maven.plugins + maven-surefire-plugin + + -Xms4g -Xmx4g + 1 + false + remote-function,textfile_reader + + /root/project/build/debug/presto_cpp/main/presto_server + + + + + + diff --git a/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestDistributedEngineOnlyQueries.java b/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestDistributedEngineOnlyQueries.java new file mode 100644 index 0000000000000..e83e4a2a6e589 --- /dev/null +++ b/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestDistributedEngineOnlyQueries.java @@ -0,0 +1,88 @@ +/* + * Licensed 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 com.facebook.presto.nativetests; + +import com.facebook.presto.nativeworker.NativeQueryRunnerUtils; +import com.facebook.presto.nativeworker.PrestoNativeQueryRunnerUtils; +import com.facebook.presto.testing.QueryRunner; +import com.facebook.presto.tests.AbstractTestEngineOnlyQueries; +import com.google.common.collect.ImmutableMap; +import org.intellij.lang.annotations.Language; +import org.testng.annotations.Test; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkState; + +public class TestDistributedEngineOnlyQueries + extends AbstractTestEngineOnlyQueries +{ + private static final String timeTypeUnsupportedError = ".*Failed to parse type \\[time.*"; + + private static final String storageFormat = "PARQUET"; + + @Override + protected QueryRunner createQueryRunner() throws Exception + { + return PrestoNativeQueryRunnerUtils.createNativeQueryRunner(ImmutableMap.of(), storageFormat); + } + + @Override + protected void createTables() + { + try { + QueryRunner javaQueryRunner = PrestoNativeQueryRunnerUtils.createJavaQueryRunner(storageFormat); + NativeQueryRunnerUtils.createAllTables(javaQueryRunner, false); + javaQueryRunner.close(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + /// TIME datatype is not supported in Prestissimo. See issue: https://github.com/prestodb/presto/issues/18844. + @Override + @Test + public void testTimeLiterals() + { + assertQueryFails("SELECT TIME '3:04:05'", timeTypeUnsupportedError); + assertQueryFails("SELECT TIME '3:04:05.123'", timeTypeUnsupportedError); + assertQueryFails("SELECT TIME '3:04:05'", timeTypeUnsupportedError); + assertQueryFails("SELECT TIME '0:04:05'", timeTypeUnsupportedError); + // TODO #7122 assertQueryFails(chicago, "SELECT TIME '3:04:05'", timeTypeUnsupportedError); + // TODO #7122 assertQueryFails(kathmandu, "SELECT TIME '3:04:05'", timeTypeUnsupportedError); + + assertQueryFails("SELECT TIME '01:02:03.400 Z'", timeTypeUnsupportedError); + assertQueryFails("SELECT TIME '01:02:03.400 UTC'", timeTypeUnsupportedError); + assertQueryFails("SELECT TIME '3:04:05 +06:00'", timeTypeUnsupportedError); + assertQueryFails("SELECT TIME '3:04:05 +0507'", timeTypeUnsupportedError); + assertQueryFails("SELECT TIME '3:04:05 +03'", timeTypeUnsupportedError); + } + + /// TIME datatype is not supported in Prestissimo. See issue: https://github.com/prestodb/presto/issues/18844. + @Override + @Test + public void testLocallyUnrepresentableTimeLiterals() + { + LocalTime localTimeThatDidNotOccurOn19700101 = LocalTime.of(0, 10); + checkState(ZoneId.systemDefault().getRules().getValidOffsets(localTimeThatDidNotOccurOn19700101.atDate(LocalDate.ofEpochDay(0))).isEmpty(), "This test assumes certain JVM time zone"); + checkState(!Objects.equals(java.sql.Time.valueOf(localTimeThatDidNotOccurOn19700101).toLocalTime(), localTimeThatDidNotOccurOn19700101), "This test assumes certain JVM time zone"); + @Language("SQL") String sql = DateTimeFormatter.ofPattern("'SELECT TIME '''HH:mm:ss''").format(localTimeThatDidNotOccurOn19700101); + assertQueryFails(sql, timeTypeUnsupportedError); + } +} diff --git a/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestOrderByQueries.java b/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestOrderByQueries.java new file mode 100644 index 0000000000000..1f8f378e2bdcf --- /dev/null +++ b/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestOrderByQueries.java @@ -0,0 +1,52 @@ +/* + * Licensed 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 com.facebook.presto.nativetests; + +import com.facebook.presto.nativeworker.NativeQueryRunnerUtils; +import com.facebook.presto.nativeworker.PrestoNativeQueryRunnerUtils; +import com.facebook.presto.testing.QueryRunner; +import com.facebook.presto.tests.AbstractTestOrderByQueries; +import com.google.common.collect.ImmutableMap; +import org.testng.annotations.Test; + +public class TestOrderByQueries + extends AbstractTestOrderByQueries +{ + private static final String storageFormat = "PARQUET"; + + @Override + protected QueryRunner createQueryRunner() throws Exception + { + return PrestoNativeQueryRunnerUtils.createNativeQueryRunner(ImmutableMap.of(), storageFormat); + } + + @Override + protected void createTables() + { + try { + QueryRunner javaQueryRunner = PrestoNativeQueryRunnerUtils.createJavaQueryRunner(storageFormat); + NativeQueryRunnerUtils.createAllTables(javaQueryRunner, false); + javaQueryRunner.close(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + /// Queries in this testcase use the apply function, which is used to test lambda expressions, and is currently + /// unsupported in Presto C++. See issue: https://github.com/prestodb/presto/issues/20741. + @Override + @Test(enabled = false) + public void testOrderByWithOutputColumnReferenceInLambdas() {} +} diff --git a/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestRepartitionQueries.java b/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestRepartitionQueries.java new file mode 100644 index 0000000000000..2bb1125f63ca5 --- /dev/null +++ b/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestRepartitionQueries.java @@ -0,0 +1,45 @@ +/* + * Licensed 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 com.facebook.presto.nativetests; + +import com.facebook.presto.nativeworker.NativeQueryRunnerUtils; +import com.facebook.presto.nativeworker.PrestoNativeQueryRunnerUtils; +import com.facebook.presto.testing.QueryRunner; +import com.facebook.presto.tests.AbstractTestRepartitionQueries; +import com.google.common.collect.ImmutableMap; + +public class TestRepartitionQueries + extends AbstractTestRepartitionQueries +{ + private static final String storageFormat = "PARQUET"; + + @Override + protected QueryRunner createQueryRunner() throws Exception + { + return PrestoNativeQueryRunnerUtils.createNativeQueryRunner(ImmutableMap.of(), storageFormat); + } + + @Override + protected void createTables() + { + try { + QueryRunner javaQueryRunner = PrestoNativeQueryRunnerUtils.createJavaQueryRunner(storageFormat); + NativeQueryRunnerUtils.createAllTables(javaQueryRunner, false); + javaQueryRunner.close(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestRepartitionQueriesWithSmallPages.java b/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestRepartitionQueriesWithSmallPages.java new file mode 100644 index 0000000000000..ba48b071c58b2 --- /dev/null +++ b/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestRepartitionQueriesWithSmallPages.java @@ -0,0 +1,47 @@ +/* + * Licensed 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 com.facebook.presto.nativetests; + +import com.facebook.presto.nativeworker.NativeQueryRunnerUtils; +import com.facebook.presto.nativeworker.PrestoNativeQueryRunnerUtils; +import com.facebook.presto.testing.QueryRunner; +import com.facebook.presto.tests.AbstractTestRepartitionQueries; +import com.google.common.collect.ImmutableMap; + +public class TestRepartitionQueriesWithSmallPages + extends AbstractTestRepartitionQueries +{ + private static final String storageFormat = "PARQUET"; + + @Override + protected QueryRunner createQueryRunner() throws Exception + { + return PrestoNativeQueryRunnerUtils.createNativeQueryRunner( + // Use small SerializedPages to force flushing + ImmutableMap.of("driver.max-page-partitioning-buffer-size", "200B"), storageFormat); + } + + @Override + protected void createTables() + { + try { + QueryRunner javaQueryRunner = PrestoNativeQueryRunnerUtils.createJavaQueryRunner(storageFormat); + NativeQueryRunnerUtils.createAllTables(javaQueryRunner, false); + javaQueryRunner.close(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestWindowQueries.java b/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestWindowQueries.java new file mode 100644 index 0000000000000..634c3b5de1e89 --- /dev/null +++ b/presto-native-tests/src/test/java/com.facebook.presto.nativetests/TestWindowQueries.java @@ -0,0 +1,225 @@ +/* + * Licensed 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 com.facebook.presto.nativetests; + +import com.facebook.presto.nativeworker.NativeQueryRunnerUtils; +import com.facebook.presto.nativeworker.PrestoNativeQueryRunnerUtils; +import com.facebook.presto.testing.QueryRunner; +import com.facebook.presto.tests.AbstractTestWindowQueries; +import com.google.common.collect.ImmutableMap; +import org.testng.annotations.Test; + +public class TestWindowQueries + extends AbstractTestWindowQueries +{ + private static final String frameTypeDiffersError = ".*Window frame of type RANGE does not match types of the ORDER BY and frame column.*"; + + private String storageFormat = "PARQUET"; + + @Override + protected QueryRunner createQueryRunner() throws Exception + { + return PrestoNativeQueryRunnerUtils.createNativeQueryRunner(ImmutableMap.of(), storageFormat); + } + + @Override + protected void createTables() + { + try { + QueryRunner javaQueryRunner = PrestoNativeQueryRunnerUtils.createJavaQueryRunner("PARQUET"); + NativeQueryRunnerUtils.createAllTables(javaQueryRunner, false); + javaQueryRunner.close(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + /// Queries in this test fail because GROUPS mode in Window frame is not supported in Prestissimo. See issue: + /// https://github.com/prestodb/presto/issues/24413. + @Override + @Test(enabled = false) + public void testAllPartitionSameValuesGroup() {} + + /// Queries in this test fail because GROUPS mode in Window frame is not supported in Prestissimo. See issue: + /// https://github.com/prestodb/presto/issues/24413. + @Override + @Test(enabled = false) + public void testConstantOffset() {} + + /// Queries in this test fail because GROUPS mode in Window frame is not supported in Prestissimo. See issue: + /// https://github.com/prestodb/presto/issues/24413. + @Override + @Test(enabled = false) + public void testEmptyFrameGroup() {} + + /// Queries in this test fail because GROUPS mode in Window frame is not supported in Prestissimo. See issue: + /// https://github.com/prestodb/presto/issues/24413. + @Override + @Test(enabled = false) + public void testInvalidOffsetGroup() {} + + /// Queries in this test fail because GROUPS mode in Window frame is not supported in Prestissimo. See issue: + /// https://github.com/prestodb/presto/issues/24413. + @Override + @Test(enabled = false) + public void testMixedTypeFrameBounds() {} + + /// Queries in this test fail because GROUPS mode in Window frame is not supported in Prestissimo. See issue: + /// https://github.com/prestodb/presto/issues/24413. + @Override + @Test(enabled = false) + public void testMultipleWindowFunctionsGroup() {} + + /// Queries in this test fail because GROUPS mode in Window frame is not supported in Prestissimo. See issue: + /// https://github.com/prestodb/presto/issues/24413. + @Override + @Test(enabled = false) + public void testNonConstantOffsetGroup() {} + + /// Queries in this test fail because GROUPS mode in Window frame is not supported in Prestissimo. See issue: + /// https://github.com/prestodb/presto/issues/24413. + @Override + @Test(enabled = false) + public void testNoValueFrameBoundsGroup() {} + + /// Queries in this test fail because GROUPS mode in Window frame is not supported in Prestissimo. See issue: + /// https://github.com/prestodb/presto/issues/24413. + @Override + @Test(enabled = false) + public void testOnlyNullsGroup() {} + + /// Queries in this test fail because GROUPS mode in Window frame is not supported in Prestissimo. See issue: + /// https://github.com/prestodb/presto/issues/24413. + @Override + @Test(enabled = false) + public void testWindowPartitioningGroup() {} + + /// This test is flaky so disabled for now, see issue: https://github.com/prestodb/presto/issues/21888. + @Override + @Test + public void testInvalidOffset() {} + + /// Queries in this test fail because the Window's ORDER BY column type differs from the frame bound type. See + /// issue: https://github.com/prestodb/presto/issues/23269. + @Override + @Test(enabled = false) + public void testEmptyFrameMixedBounds() {} + + /// Queries in this test fail because the Window's ORDER BY column type differs from the frame bound type. See + /// issue: https://github.com/prestodb/presto/issues/23269. + @Override + @Test(enabled = false) + public void testMixedTypeFrameBoundsAscendingNullsFirst() {} + + /// Queries in this test fail because the Window's ORDER BY column type differs from the frame bound type. See + /// issue: https://github.com/prestodb/presto/issues/23269. + @Override + @Test(enabled = false) + public void testMixedTypeFrameBoundsAscendingNullsLast() {} + + /// Queries in this test fail because the Window's ORDER BY column type differs from the frame bound type. See + /// issue: https://github.com/prestodb/presto/issues/23269. + @Override + @Test(enabled = false) + public void testMixedTypeFrameBoundsDescendingNullsFirst() {} + + /// Queries in this test fail because the Window's ORDER BY column type differs from the frame bound type. See + /// issue: https://github.com/prestodb/presto/issues/23269. + @Override + @Test(enabled = false) + public void testMixedTypeFrameBoundsDescendingNullsLast() {} + + /// Queries in this test fail because the Window's ORDER BY column type differs from the frame bound type. See + /// issue: https://github.com/prestodb/presto/issues/23269. + @Override + @Test(enabled = false) + public void testNonConstantOffset() {} + + /// Queries in this test fail because the Window's ORDER BY column type differs from the frame bound type. See + /// issue: https://github.com/prestodb/presto/issues/23269. + @Override + @Test(enabled = false) + public void testWindowPartitioning() {} + + /// The last query in this test fails because the Window's ORDER BY column type differs from the frame bound type. + /// See issue: https://github.com/prestodb/presto/issues/23269. + @Override + @Test + public void testMultipleWindowFunctions() + { + assertQuery("SELECT x, array_agg(date) OVER(ORDER BY x RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING), avg(number) OVER(ORDER BY x RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING) " + + "FROM (VALUES " + + "(2, DATE '2222-01-01', 4.4), " + + "(1, DATE '1111-01-01', 2.2), " + + "(3, DATE '3333-01-01', 6.6)) T(x, date, number)", + "VALUES " + + "(1, ARRAY[DATE '1111-01-01', DATE '2222-01-01'], 3.3), " + + "(2, ARRAY[DATE '1111-01-01', DATE '2222-01-01', DATE '3333-01-01'], 4.4), " + + "(3, ARRAY[DATE '2222-01-01', DATE '3333-01-01'], 5.5)"); + + assertQueryFails("SELECT x, array_agg(a) OVER(ORDER BY x RANGE BETWEEN 2 PRECEDING AND CURRENT ROW), array_agg(a) OVER(ORDER BY x RANGE BETWEEN CURRENT ROW AND 2 FOLLOWING) " + + "FROM (VALUES " + + "(1.0, 1), " + + "(2.0, 2), " + + "(3.0, 3), " + + "(4.0, 4), " + + "(5.0, 5), " + + "(6.0, 6)) T(x, a)", frameTypeDiffersError); + } + + /// The first query in this test fails because the Window's ORDER BY column type differs from the frame bound type. + /// See issue: https://github.com/prestodb/presto/issues/23269. + /// The last query in this test fails because `plus` arithmetic function is currently unsupported for INTERVAL YEAR + /// MONTH type in Velox. See PR: https://github.com/facebookincubator/velox/pull/11612. + @Override + @Test + public void testTypes() + { + assertQueryFails("SELECT array_agg(a) OVER(ORDER BY a RANGE BETWEEN DOUBLE '0.5' PRECEDING AND TINYINT '1' FOLLOWING) " + + "FROM (VALUES 1, null, 2) T(a)", + frameTypeDiffersError); + + assertQuery("SELECT array_agg(a) OVER(ORDER BY a RANGE BETWEEN 0.5 PRECEDING AND 1.000 FOLLOWING) " + + "FROM (VALUES REAL '1', null, 2) T(a)", + "VALUES " + + "ARRAY[CAST('1' AS REAL), CAST('2' AS REAL)], " + + "ARRAY[CAST('2' AS REAL)], " + + "ARRAY[null]"); + + assertQuery("SELECT x, array_agg(x) OVER(ORDER BY x DESC RANGE BETWEEN interval '1' month PRECEDING AND interval '1' month FOLLOWING) " + + "FROM (VALUES DATE '2001-01-31', DATE '2001-08-25', DATE '2001-09-25', DATE '2001-09-26') T(x)", + "VALUES " + + "(DATE '2001-09-26', ARRAY[DATE '2001-09-26', DATE '2001-09-25']), " + + "(DATE '2001-09-25', ARRAY[DATE '2001-09-26', DATE '2001-09-25', DATE '2001-08-25']), " + + "(DATE '2001-08-25', ARRAY[DATE '2001-09-25', DATE '2001-08-25']), " + + "(DATE '2001-01-31', ARRAY[DATE '2001-01-31'])"); + + // January 31 + 1 month sets the frame bound to the last day of February. March 1 is out of range. + assertQuery("SELECT x, array_agg(x) OVER(ORDER BY x RANGE BETWEEN CURRENT ROW AND interval '1' month FOLLOWING) " + + "FROM (VALUES DATE '2001-01-31', DATE '2001-02-28', DATE '2001-03-01') T(x)", + "VALUES " + + "(DATE '2001-01-31', ARRAY[DATE '2001-01-31', DATE '2001-02-28']), " + + "(DATE '2001-02-28', ARRAY[DATE '2001-02-28', DATE '2001-03-01']), " + + "(DATE '2001-03-01', ARRAY[DATE '2001-03-01'])"); + + // H2 and Presto has some type conversion problem for Interval type, hence use the same query runner for this query + assertQueryFails("SELECT x, array_agg(x) OVER(ORDER BY x RANGE BETWEEN interval '1' year PRECEDING AND interval '1' month FOLLOWING) " + + "FROM (VALUES " + + "INTERVAL '1' month, " + + "INTERVAL '2' month, " + + "INTERVAL '5' year) T(x)", + ".*Scalar function presto.default.plus not registered with arguments.*", true); + } +} diff --git a/presto-tests/src/test/java/com/facebook/presto/tests/AbstractTestEngineOnlyQueries.java b/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestEngineOnlyQueries.java similarity index 90% rename from presto-tests/src/test/java/com/facebook/presto/tests/AbstractTestEngineOnlyQueries.java rename to presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestEngineOnlyQueries.java index c2020d8bac6a2..997afe8cabf79 100644 --- a/presto-tests/src/test/java/com/facebook/presto/tests/AbstractTestEngineOnlyQueries.java +++ b/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestEngineOnlyQueries.java @@ -35,7 +35,7 @@ public abstract class AbstractTestEngineOnlyQueries extends AbstractTestQueryFramework { @Test - public void testTimeLiterals() + public void testDateLiterals() { Session chicago = Session.builder(getSession()).setTimeZoneKey(TimeZoneKey.getTimeZoneKey("America/Chicago")).build(); Session kathmandu = Session.builder(getSession()).setTimeZoneKey(TimeZoneKey.getTimeZoneKey("Asia/Kathmandu")).build(); @@ -44,7 +44,11 @@ public void testTimeLiterals() assertQuery("SELECT DATE '2013-03-22'"); assertQuery(chicago, "SELECT DATE '2013-03-22'"); assertQuery(kathmandu, "SELECT DATE '2013-03-22'"); + } + @Test + public void testTimeLiterals() + { assertEquals(computeScalar("SELECT TIME '3:04:05'"), LocalTime.of(3, 4, 5, 0)); assertEquals(computeScalar("SELECT TIME '3:04:05.123'"), LocalTime.of(3, 4, 5, 123_000_000)); assertQuery("SELECT TIME '3:04:05'"); @@ -57,7 +61,11 @@ public void testTimeLiterals() assertEquals(computeScalar("SELECT TIME '3:04:05 +06:00'"), OffsetTime.of(3, 4, 5, 0, ZoneOffset.ofHoursMinutes(6, 0))); assertEquals(computeScalar("SELECT TIME '3:04:05 +0507'"), OffsetTime.of(3, 4, 5, 0, ZoneOffset.ofHoursMinutes(5, 7))); assertEquals(computeScalar("SELECT TIME '3:04:05 +03'"), OffsetTime.of(3, 4, 5, 0, ZoneOffset.ofHoursMinutes(3, 0))); + } + @Test + public void testTimestampLiterals() + { assertEquals(computeScalar("SELECT TIMESTAMP '1960-01-22 3:04:05'"), LocalDateTime.of(1960, 1, 22, 3, 4, 5)); assertEquals(computeScalar("SELECT TIMESTAMP '1960-01-22 3:04:05.123'"), LocalDateTime.of(1960, 1, 22, 3, 4, 5, 123_000_000)); assertQuery("SELECT TIMESTAMP '1960-01-22 3:04:05'"); @@ -69,27 +77,35 @@ public void testTimeLiterals() } @Test - public void testLocallyUnrepresentableTimeLiterals() + public void testLocallyUnrepresentableDateLiterals() { - LocalDateTime localTimeThatDidNotExist = LocalDateTime.of(2017, 4, 2, 2, 10); - checkState(ZoneId.systemDefault().getRules().getValidOffsets(localTimeThatDidNotExist).isEmpty(), "This test assumes certain JVM time zone"); - // This tests that both Presto runner and H2 can return TIMESTAMP value that never happened in JVM's zone (e.g. is not representable using java.sql.Timestamp) - @Language("SQL") String sql = DateTimeFormatter.ofPattern("'SELECT TIMESTAMP '''uuuu-MM-dd HH:mm:ss''").format(localTimeThatDidNotExist); - assertEquals(computeScalar(sql), localTimeThatDidNotExist); // this tests Presto and the QueryRunner - assertQuery(sql); // this tests H2QueryRunner - LocalDate localDateThatDidNotHaveMidnight = LocalDate.of(1970, 1, 1); checkState(ZoneId.systemDefault().getRules().getValidOffsets(localDateThatDidNotHaveMidnight.atStartOfDay()).isEmpty(), "This test assumes certain JVM time zone"); // This tests that both Presto runner and H2 can return DATE value for a day which midnight never happened in JVM's zone (e.g. is not exactly representable using java.sql.Date) - sql = DateTimeFormatter.ofPattern("'SELECT DATE '''uuuu-MM-dd''").format(localDateThatDidNotHaveMidnight); + @Language("SQL") String sql = DateTimeFormatter.ofPattern("'SELECT DATE '''uuuu-MM-dd''").format(localDateThatDidNotHaveMidnight); assertEquals(computeScalar(sql), localDateThatDidNotHaveMidnight); // this tests Presto and the QueryRunner assertQuery(sql); // this tests H2QueryRunner + } + @Test + public void testLocallyUnrepresentableTimeLiterals() + { LocalTime localTimeThatDidNotOccurOn19700101 = LocalTime.of(0, 10); checkState(ZoneId.systemDefault().getRules().getValidOffsets(localTimeThatDidNotOccurOn19700101.atDate(LocalDate.ofEpochDay(0))).isEmpty(), "This test assumes certain JVM time zone"); checkState(!Objects.equals(java.sql.Time.valueOf(localTimeThatDidNotOccurOn19700101).toLocalTime(), localTimeThatDidNotOccurOn19700101), "This test assumes certain JVM time zone"); - sql = DateTimeFormatter.ofPattern("'SELECT TIME '''HH:mm:ss''").format(localTimeThatDidNotOccurOn19700101); + @Language("SQL") String sql = DateTimeFormatter.ofPattern("'SELECT TIME '''HH:mm:ss''").format(localTimeThatDidNotOccurOn19700101); assertEquals(computeScalar(sql), localTimeThatDidNotOccurOn19700101); // this tests Presto and the QueryRunner assertQuery(sql); // this tests H2QueryRunner } + + @Test + public void testLocallyUnrepresentableTimestampLiterals() + { + LocalDateTime localTimeThatDidNotExist = LocalDateTime.of(2017, 4, 2, 2, 10); + checkState(ZoneId.systemDefault().getRules().getValidOffsets(localTimeThatDidNotExist).isEmpty(), "This test assumes certain JVM time zone"); + // This tests that both Presto runner and H2 can return TIMESTAMP value that never happened in JVM's zone (e.g. is not representable using java.sql.Timestamp) + @Language("SQL") String sql = DateTimeFormatter.ofPattern("'SELECT TIMESTAMP '''uuuu-MM-dd HH:mm:ss''").format(localTimeThatDidNotExist); + assertEquals(computeScalar(sql), localTimeThatDidNotExist); // this tests Presto and the QueryRunner + assertQuery(sql); // this tests H2QueryRunner + } } diff --git a/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestOrderByQueries.java b/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestOrderByQueries.java index 9e633ff52e9ee..62a086c951bfe 100644 --- a/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestOrderByQueries.java +++ b/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestOrderByQueries.java @@ -86,11 +86,6 @@ public void testOrderByWithOutputColumnReference() assertQueryOrdered("SELECT -a AS a, a AS b FROM (VALUES 1, 2) t(a) GROUP BY a ORDER BY t.a+2*a", "VALUES (-2, 2), (-1, 1)"); assertQueryOrdered("SELECT -a AS a, a AS b FROM (VALUES 1, 2) t(a) GROUP BY t.a ORDER BY t.a+2*a", "VALUES (-2, 2), (-1, 1)"); - // lambdas - assertQueryOrdered("SELECT x AS y FROM (values (1,2), (2,3)) t(x, y) GROUP BY x ORDER BY apply(x, x -> -x) + 2*x", "VALUES 1, 2"); - assertQueryOrdered("SELECT -y AS x FROM (values (1,2), (2,3)) t(x, y) GROUP BY y ORDER BY apply(x, x -> -x)", "VALUES -2, -3"); - assertQueryOrdered("SELECT -y AS x FROM (values (1,2), (2,3)) t(x, y) GROUP BY y ORDER BY sum(apply(-y, x -> x * 1.0))", "VALUES -3, -2"); - // distinct assertQueryOrdered("SELECT DISTINCT -a AS b FROM (VALUES 1, 2) t(a) ORDER BY b", "VALUES -2, -1"); assertQueryOrdered("SELECT DISTINCT -a AS b FROM (VALUES 1, 2) t(a) ORDER BY 1", "VALUES -2, -1"); @@ -106,6 +101,14 @@ public void testOrderByWithOutputColumnReference() assertQueryFails("SELECT a, a* -1 AS a FROM (VALUES -1, 0, 2) t(a) ORDER BY a", ".*'a' is ambiguous"); } + @Test + public void testOrderByWithOutputColumnReferenceInLambdas() + { + assertQueryOrdered("SELECT x AS y FROM (values (1,2), (2,3)) t(x, y) GROUP BY x ORDER BY apply(x, x -> -x) + 2*x", "VALUES 1, 2"); + assertQueryOrdered("SELECT -y AS x FROM (values (1,2), (2,3)) t(x, y) GROUP BY y ORDER BY apply(x, x -> -x)", "VALUES -2, -3"); + assertQueryOrdered("SELECT -y AS x FROM (values (1,2), (2,3)) t(x, y) GROUP BY y ORDER BY sum(apply(-y, x -> x * 1.0))", "VALUES -3, -2"); + } + @Test public void testOrderByWithAggregation() { diff --git a/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestQueryFramework.java b/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestQueryFramework.java index cdc974260c6f3..c134b2fd56027 100644 --- a/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestQueryFramework.java +++ b/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestQueryFramework.java @@ -316,6 +316,11 @@ protected void assertQueryFails(QueryRunner queryRunner, @Language("SQL") String QueryAssertions.assertQueryFails(queryRunner, getSession(), sql, expectedMessageRegExp); } + protected void assertQueryFails(@Language("SQL") String sql, @Language("RegExp") String expectedMessageRegExp, boolean usePatternMatcher) + { + QueryAssertions.assertQueryFails(queryRunner, getSession(), sql, expectedMessageRegExp, usePatternMatcher); + } + protected void assertQueryFails(Session session, @Language("SQL") String sql, @Language("RegExp") String expectedMessageRegExp) { QueryAssertions.assertQueryFails(queryRunner, session, sql, expectedMessageRegExp); @@ -336,6 +341,11 @@ protected void assertQueryError(@Language("SQL") String sql, @Language("RegExp") assertQueryError(queryRunner, getSession(), sql, expectedMessageRegExp); } + protected void assertQueryFails(Session session, @Language("SQL") String sql, @Language("RegExp") String expectedMessageRegExp, boolean usePatternMatcher) + { + QueryAssertions.assertQueryFails(queryRunner, session, sql, expectedMessageRegExp, usePatternMatcher); + } + protected void assertQueryReturnsEmptyResult(@Language("SQL") String sql) { QueryAssertions.assertQueryReturnsEmptyResult(queryRunner, getSession(), sql); diff --git a/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestWindowQueries.java b/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestWindowQueries.java index 14fa5afa8ebf3..1fc7d25953853 100644 --- a/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestWindowQueries.java +++ b/presto-tests/src/main/java/com/facebook/presto/tests/AbstractTestWindowQueries.java @@ -1016,7 +1016,7 @@ public void testEmptyInput() } @Test - public void testEmptyFrame() + public void testEmptyFrameIntegralBounds() { assertQuery("SELECT array_agg(a) OVER(ORDER BY a DESC NULLS LAST RANGE BETWEEN 1 PRECEDING AND 10 PRECEDING) " + "FROM (VALUES 1, 2, 3, null, null, 2, 1, null, null) T(a)", @@ -1044,6 +1044,23 @@ public void testEmptyFrame() "ARRAY[null, null, null, null], " + "ARRAY[null, null, null, null]"); + assertQuery("SELECT array_agg(a) OVER(ORDER BY a RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING) " + + "FROM (VALUES 1, 2) T(a)", + "VALUES " + + "null, " + + "ARRAY[1]"); + + assertQuery("SELECT array_agg(a) OVER(ORDER BY a NULLS FIRST RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING) " + + "FROM (VALUES null, 1, 2) T(a)", + "VALUES " + + "ARRAY[null], " + + "null, " + + "ARRAY[1]"); + } + + @Test + public void testEmptyFrameMixedBounds() + { assertQuery("SELECT array_agg(a) OVER(ORDER BY a RANGE BETWEEN 0.5 FOLLOWING AND 1.5 FOLLOWING) " + "FROM (VALUES 1, 2, 4) T(a)", "VALUES " + @@ -1077,19 +1094,6 @@ public void testEmptyFrame() "null, " + "null"); - assertQuery("SELECT array_agg(a) OVER(ORDER BY a RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING) " + - "FROM (VALUES 1, 2) T(a)", - "VALUES " + - "null, " + - "ARRAY[1]"); - - assertQuery("SELECT array_agg(a) OVER(ORDER BY a NULLS FIRST RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING) " + - "FROM (VALUES null, 1, 2) T(a)", - "VALUES " + - "ARRAY[null], " + - "null, " + - "ARRAY[1]"); - assertQuery("SELECT array_agg(a) OVER(ORDER BY a NULLS FIRST RANGE BETWEEN 2 PRECEDING AND 1.5 PRECEDING) " + "FROM (VALUES null, 1, 2) T(a)", "VALUES " + diff --git a/presto-tests/src/main/java/com/facebook/presto/tests/QueryAssertions.java b/presto-tests/src/main/java/com/facebook/presto/tests/QueryAssertions.java index 035051acf2866..9a1cdbe8b7755 100644 --- a/presto-tests/src/main/java/com/facebook/presto/tests/QueryAssertions.java +++ b/presto-tests/src/main/java/com/facebook/presto/tests/QueryAssertions.java @@ -37,6 +37,7 @@ import java.util.OptionalLong; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.regex.Pattern; import static com.google.common.base.Strings.nullToEmpty; import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly; @@ -346,7 +347,18 @@ protected static void assertQueryFails(QueryRunner queryRunner, Session session, fail(format("Expected query to fail: %s", sql)); } catch (RuntimeException ex) { - assertExceptionMessage(sql, ex, expectedMessageRegExp); + assertExceptionMessage(sql, ex, expectedMessageRegExp, false); + } + } + + protected static void assertQueryFails(QueryRunner queryRunner, Session session, @Language("SQL") String sql, @Language("RegExp") String expectedMessageRegExp, boolean usePatternMatcher) + { + try { + queryRunner.execute(session, sql); + fail(format("Expected query to fail: %s", sql)); + } + catch (RuntimeException ex) { + assertExceptionMessage(sql, ex, expectedMessageRegExp, usePatternMatcher); } } @@ -362,10 +374,18 @@ protected static void assertQueryReturnsEmptyResult(QueryRunner queryRunner, Ses } } - private static void assertExceptionMessage(String sql, Exception exception, @Language("RegExp") String regex) + private static void assertExceptionMessage(String sql, Exception exception, @Language("RegExp") String regex, boolean usePatternMatcher) { - if (!nullToEmpty(exception.getMessage()).matches(regex)) { - fail(format("Expected exception message '%s' to match '%s' for query: %s", exception.getMessage(), regex, sql), exception); + if (usePatternMatcher) { + Pattern p = Pattern.compile(regex, Pattern.MULTILINE); + if (!(p.matcher(exception.getMessage()).find())) { + fail(format("Expected exception message '%s' to match '%s' for query: %s", exception.getMessage(), regex, sql), exception); + } + } + else { + if (!nullToEmpty(exception.getMessage()).matches(regex)) { + fail(format("Expected exception message '%s' to match '%s' for query: %s", exception.getMessage(), regex, sql), exception); + } } }